From 7d6c5167626151ed1a687cff5de5930331517e0a Mon Sep 17 00:00:00 2001 From: Lee Saferite Date: Thu, 31 Dec 2009 11:20:09 -0500 Subject: [PATCH] Import Magento 1.4.0.0-alpha2 --- app/Mage.php | 35 +- app/code/core/Mage/Admin/Model/Config.php | 50 +- .../core/Mage/Admin/Model/Mysql4/Rules.php | 49 +- app/code/core/Mage/Admin/Model/Roles.php | 8 +- app/code/core/Mage/Admin/Model/User.php | 31 +- app/code/core/Mage/Admin/etc/config.xml | 9 - .../Mage/AdminNotification/Model/Observer.php | 12 +- .../Mage/AdminNotification/etc/adminhtml.xml | 74 + .../Mage/AdminNotification/etc/config.xml | 59 - .../Block/Api/User/Edit/Tab/Main.php | 4 +- .../Block/Catalog/Category/Edit/Form.php | 3 + .../Block/Catalog/Category/Widget/Chooser.php | 173 + .../Form/Renderer/Fieldset/Element.php | 2 +- .../Product/Attribute/Edit/Tab/Main.php | 322 +- .../Product/Attribute/Edit/Tab/Options.php | 151 +- .../Block/Catalog/Product/Attribute/Grid.php | 98 +- .../Catalog/Product/Attribute/Set/Main.php | 12 +- .../Product/Attribute/Set/Main/Formset.php | 2 +- .../Product/Edit/Tab/Ajax/Serializer.php | 16 + .../Catalog/Product/Edit/Tab/Crosssell.php | 146 +- .../Product/Edit/Tab/Options/Option.php | 17 +- .../Edit/Tab/Options/Type/Abstract.php | 6 +- .../Catalog/Product/Edit/Tab/Price/Tier.php | 271 +- .../Catalog/Product/Edit/Tab/Related.php | 155 +- .../Super/Config/Grid/Filter/Inventory.php | 2 +- .../Catalog/Product/Edit/Tab/Super/Group.php | 22 +- .../Block/Catalog/Product/Edit/Tab/Tag.php | 15 +- .../Catalog/Product/Edit/Tab/Tag/Customer.php | 8 +- .../Block/Catalog/Product/Edit/Tab/Upsell.php | 131 +- .../Block/Catalog/Product/Widget/Chooser.php | 288 + .../Block/Catalog/Search/Edit/Form.php | 5 +- .../Adminhtml/Block/Catalog/Search/Grid.php | 4 +- .../Adminhtml/Block/Cms/Block/Edit/Form.php | 13 +- .../Block/Cms/Block/Widget/Chooser.php | 158 + .../core/Mage/Adminhtml/Block/Cms/Page.php | 20 +- .../Mage/Adminhtml/Block/Cms/Page/Edit.php | 39 +- .../Block/Cms/Page/Edit/Tab/Content.php | 146 + .../Block/Cms/Page/Edit/Tab/Design.php | 139 +- .../Block/Cms/Page/Edit/Tab/Main.php | 102 +- .../Block/Cms/Page/Edit/Tab/Meta.php | 71 +- .../Adminhtml/Block/Cms/Page/Edit/Tabs.php | 32 - .../Mage/Adminhtml/Block/Cms/Page/Grid.php | 5 +- .../Block/Cms/Page/Widget/Chooser.php | 157 + .../core/Mage/Adminhtml/Block/Cms/Widget.php | 57 + .../Adminhtml/Block/Cms/Widget/Chooser.php | 194 + .../Mage/Adminhtml/Block/Cms/Widget/Form.php | 176 + .../Adminhtml/Block/Cms/Widget/Options.php | 177 + .../Block/Cms/Wysiwyg/Images/Content.php | 132 + .../Cms/Wysiwyg/Images/Content/Files.php | 89 + .../Cms/Wysiwyg/Images/Content/Newfolder.php | 37 + .../Cms/Wysiwyg/Images/Content/Uploader.php | 59 + .../Block/Cms/Wysiwyg/Images/Tree.php | 98 + .../Block/Customer/Edit/Renderer/Region.php | 33 +- .../Block/Customer/Edit/Tab/Account.php | 10 +- .../Block/Customer/Edit/Tab/Addresses.php | 13 +- .../Adminhtml/Block/Customer/Edit/Tab/Tag.php | 3 +- .../Block/Customer/Edit/Tab/Wishlist.php | 15 +- .../Adminhtml/Block/Dashboard/Orders/Grid.php | 4 +- .../Mage/Adminhtml/Block/Dashboard/Totals.php | 9 +- .../Mage/Adminhtml/Block/Media/Uploader.php | 21 + .../Adminhtml/Block/Notification/Baseurl.php | 64 + .../Notification/Grid/Renderer/Severity.php | 2 +- .../core/Mage/Adminhtml/Block/Page/Menu.php | 9 +- .../Block/Permissions/User/Edit/Tab/Roles.php | 11 +- .../Adminhtml/Block/Promo/Catalog/Edit.php | 6 + .../Block/Promo/Catalog/Edit/Tab/Actions.php | 43 +- .../Promo/Catalog/Edit/Tab/Conditions.php | 44 +- .../Block/Promo/Catalog/Edit/Tab/Main.php | 43 +- .../Block/Promo/Catalog/Edit/Tabs.php | 25 - .../Mage/Adminhtml/Block/Promo/Quote/Edit.php | 7 + .../Block/Promo/Quote/Edit/Tab/Actions.php | 44 +- .../Block/Promo/Quote/Edit/Tab/Conditions.php | 44 +- .../Block/Promo/Quote/Edit/Tab/Labels.php | 44 +- .../Block/Promo/Quote/Edit/Tab/Main.php | 43 +- .../Adminhtml/Block/Promo/Quote/Edit/Tabs.php | 27 - .../Block/Promo/Widget/Chooser/Daterange.php | 142 + .../Block/Report/Review/Customer/Grid.php | 3 +- .../Block/Report/Review/Product/Grid.php | 3 +- .../Block/Report/Sales/Sales/Grid.php | 10 + .../Adminhtml/Block/Report/Search/Grid.php | 2 +- .../Block/Report/Tag/Customer/Grid.php | 1 + .../Block/Report/Tag/Popular/Grid.php | 1 + .../Block/Report/Tag/Product/Grid.php | 1 + .../Adminhtml/Block/Sales/Invoice/Grid.php | 5 +- .../Order/Create/Billing/Method/Form.php | 12 +- .../Block/Sales/Order/Create/Form/Address.php | 6 +- .../Block/Sales/Order/Creditmemo/View.php | 19 +- .../Block/Sales/Order/Invoice/View.php | 15 + .../Block/Sales/Order/Shipment/View.php | 7 +- .../Block/Sales/Order/Totals/Item.php | 37 +- .../Mage/Adminhtml/Block/Sales/Order/View.php | 12 + .../Mage/Adminhtml/Block/Sales/Totals.php | 2 +- .../Adminhtml/Block/Tag/Assigned/Grid.php | 271 + .../Adminhtml/Block/Tag/Customer/Grid.php | 38 +- .../core/Mage/Adminhtml/Block/Tag/Edit.php | 100 +- .../Adminhtml/Block/Tag/Edit/Accordion.php | 64 + .../Adminhtml/Block/Tag/Edit/Assigned.php | 58 + .../Mage/Adminhtml/Block/Tag/Edit/Form.php | 41 +- .../Mage/Adminhtml/Block/Tag/Product/Grid.php | 27 +- .../Adminhtml/Block/Tag/Store/Switcher.php | 51 + .../core/Mage/Adminhtml/Block/Tag/Tag.php | 2 +- .../Mage/Adminhtml/Block/Tag/Tag/Edit.php | 24 + .../Adminhtml/Block/Tag/Tag/Edit/Form.php | 7 +- .../Mage/Adminhtml/Block/Tag/Tag/Grid.php | 47 +- .../Mage/Adminhtml/Block/Tax/Rate/Form.php | 1 + .../Mage/Adminhtml/Block/Tax/Rule/Edit.php | 8 + app/code/core/Mage/Adminhtml/Block/Widget.php | 13 +- .../Mage/Adminhtml/Block/Widget/Button.php | 2 +- .../Mage/Adminhtml/Block/Widget/Container.php | 74 +- .../core/Mage/Adminhtml/Block/Widget/Form.php | 8 +- .../core/Mage/Adminhtml/Block/Widget/Grid.php | 91 +- .../Adminhtml/Block/Widget/Grid/Column.php | 6 + .../Block/Widget/Grid/Column/Filter/Store.php | 2 +- .../Block/Widget/Grid/Column/Filter/Theme.php | 121 + .../Widget/Grid/Column/Renderer/Abstract.php | 2 +- .../Widget/Grid/Column/Renderer/Input.php | 6 +- .../Widget/Grid/Column/Renderer/Theme.php | 102 + .../Block/Widget/Grid/Massaction/Abstract.php | 38 +- .../Block/Widget/Grid/Serializer.php | 129 + .../core/Mage/Adminhtml/Block/Widget/Tabs.php | 47 +- .../core/Mage/Adminhtml/Controller/Action.php | 24 +- .../Adminhtml/Controller/Sales/Creditmemo.php | 16 + .../Adminhtml/Controller/Sales/Invoice.php | 31 +- .../Mage/Adminhtml/Helper/Dashboard/Data.php | 2 +- app/code/core/Mage/Adminhtml/Helper/Js.php | 24 +- app/code/core/Mage/Adminhtml/Model/Config.php | 2 +- .../Adminhtml/Model/Extension/Collection.php | 2 + .../Adminhtml/Model/Sales/Order/Create.php | 37 +- .../System/Config/Backend/Price/Scope.php | 4 +- .../System/Config/Backend/Seo/Product.php | 9 +- .../Model/System/Config/Clone/Media/Image.php | 4 +- .../Model/System/Config/Source/Admin/Page.php | 2 +- .../Config/Source/Cms/Wysiwyg/Enabled.php | 53 + .../Catalog/Category/WidgetController.php | 72 + .../Catalog/CategoryController.php | 45 +- .../Product/Action/AttributeController.php | 93 +- .../Catalog/Product/AttributeController.php | 9 +- .../Catalog/Product/WidgetController.php | 66 + .../controllers/Catalog/ProductController.php | 149 +- .../Cms/Block/WidgetController.php | 48 + .../controllers/Cms/BlockController.php | 8 +- .../controllers/Cms/Page/WidgetController.php | 48 + .../controllers/Cms/PageController.php | 89 +- .../controllers/Cms/WidgetController.php | 110 + .../Cms/Wysiwyg/ImagesController.php | 187 + .../controllers/Cms/WysiwygController.php | 62 + .../controllers/CustomerController.php | 14 +- .../controllers/DashboardController.php | 2 +- .../Permissions/UserController.php | 10 + .../controllers/Promo/CatalogController.php | 44 +- .../controllers/Promo/QuoteController.php | 44 +- .../controllers/Sales/OrderController.php | 11 + .../System/Email/TemplateController.php | 8 +- .../Adminhtml/controllers/TagController.php | 170 +- .../controllers/Tax/RuleController.php | 7 +- .../core/Mage/Adminhtml/etc/adminhtml.xml | 276 + app/code/core/Mage/Adminhtml/etc/config.xml | 254 +- .../Mage/AmazonPayments/Model/Payment/Asp.php | 75 +- .../Model/Payment/Asp/Notification.php | 9 +- .../Mage/AmazonPayments/Model/Payment/Cba.php | 12 +- .../core/Mage/AmazonPayments/etc/config.xml | 13 - .../core/Mage/AmazonPayments/etc/system.xml | 4 +- app/code/core/Mage/Api/etc/adminhtml.xml | 82 + app/code/core/Mage/Api/etc/config.xml | 66 - app/code/core/Mage/Api/etc/wsdl2.xml | 4 +- app/code/core/Mage/Backup/Model/Mysql4/Db.php | 4 +- app/code/core/Mage/Backup/etc/adminhtml.xml | 62 + app/code/core/Mage/Backup/etc/config.xml | 46 - .../core/Mage/Bundle/Model/Mysql4/Bundle.php | 15 + .../Bundle/Model/Mysql4/Indexer/Price.php | 438 + .../Bundle/Model/Mysql4/Indexer/Stock.php | 199 + .../Mage/Bundle/Model/Mysql4/Price/Index.php | 4 +- .../core/Mage/Bundle/Model/Product/Price.php | 1 - .../core/Mage/Bundle/Model/Product/Type.php | 26 +- app/code/core/Mage/Bundle/etc/config.xml | 27 +- .../mysql4-upgrade-0.1.8-0.1.9.php | 50 + .../mysql4-upgrade-0.1.9-0.1.10.php | 39 + .../Catalog/Block/Category/Widget/Link.php | 46 + .../core/Mage/Catalog/Block/Navigation.php | 37 +- .../Mage/Catalog/Block/Product/Abstract.php | 2 +- .../Catalog/Block/Product/Compare/List.php | 18 + .../core/Mage/Catalog/Block/Product/New.php | 50 +- .../core/Mage/Catalog/Block/Product/View.php | 59 +- .../Block/Product/View/Options/Type/Date.php | 2 +- .../Block/Product/View/Type/Configurable.php | 2 +- .../Catalog/Block/Product/Widget/Link.php | 46 + .../Mage/Catalog/Block/Product/Widget/New.php | 50 + .../core/Mage/Catalog/Block/Widget/Link.php | 104 + app/code/core/Mage/Catalog/Helper/Image.php | 135 +- app/code/core/Mage/Catalog/Helper/Product.php | 3 +- .../core/Mage/Catalog/Helper/Product/Url.php | 80 +- app/code/core/Mage/Catalog/Model/Abstract.php | 2 +- .../core/Mage/Catalog/Model/Api/Resource.php | 23 +- app/code/core/Mage/Catalog/Model/Category.php | 80 +- .../core/Mage/Catalog/Model/Category/Api.php | 32 +- .../Category/Attribute/Backend/Sortby.php | 189 +- .../Category/Attribute/Backend/Urlkey.php | 10 +- .../Category/Attribute/Source/Sortby.php | 136 +- .../Catalog/Model/Category/Indexer/Flat.php | 224 + .../Model/Category/Indexer/Product.php | 200 + app/code/core/Mage/Catalog/Model/Config.php | 30 +- .../Catalog/Model/Convert/Adapter/Product.php | 17 +- .../Catalog/Model/Convert/Parser/Product.php | 7 +- .../core/Mage/Catalog/Model/Indexer/Url.php | 226 + app/code/core/Mage/Catalog/Model/Layer.php | 19 +- .../Catalog/Model/Layer/Filter/Abstract.php | 54 +- .../Catalog/Model/Layer/Filter/Attribute.php | 31 +- .../Mage/Catalog/Model/Layer/Filter/Price.php | 98 +- app/code/core/Mage/Catalog/Model/Observer.php | 54 +- app/code/core/Mage/Catalog/Model/Product.php | 43 +- .../Mage/Catalog/Model/Product/Action.php | 123 + .../core/Mage/Catalog/Model/Product/Api.php | 12 +- .../Mage/Catalog/Model/Product/Api/V2.php | 8 +- .../Model/Product/Attribute/Backend/Media.php | 2 +- .../Product/Attribute/Backend/Tierprice.php | 221 +- .../Product/Attribute/Backend/Urlkey.php | 10 +- .../Model/Product/Attribute/Media/Api.php | 45 +- .../Model/Product/Attribute/Media/Api/V2.php | 8 +- .../Model/Product/Attribute/Tierprice/Api.php | 30 +- .../Product/Attribute/Tierprice/Api/V2.php | 4 +- .../core/Mage/Catalog/Model/Product/Image.php | 202 +- .../Catalog/Model/Product/Indexer/Eav.php | 259 + .../Catalog/Model/Product/Indexer/Flat.php | 287 + .../Catalog/Model/Product/Indexer/Price.php | 238 + .../Catalog/Model/Product/Indexer/Status.php | 25 + .../core/Mage/Catalog/Model/Product/Link.php | 8 +- .../Mage/Catalog/Model/Product/Link/Api.php | 41 +- .../Catalog/Model/Product/Link/Api/V2.php | 8 +- .../Model/Product/Option/Type/Select.php | 92 +- .../Mage/Catalog/Model/Product/Status.php | 39 +- .../core/Mage/Catalog/Model/Product/Type.php | 36 + .../Catalog/Model/Product/Type/Abstract.php | 11 +- .../Model/Product/Type/Configurable.php | 24 +- .../Product/Type/Configurable/Attribute.php | 19 +- .../Catalog/Model/Product/Type/Grouped.php | 1 + .../Mage/Catalog/Model/Product/Visibility.php | 2 +- .../Mage/Catalog/Model/Product/Website.php | 16 - .../Catalog/Model/Resource/Eav/Attribute.php | 105 + .../Model/Resource/Eav/Mysql4/Abstract.php | 408 +- .../Model/Resource/Eav/Mysql4/Attribute.php | 52 + .../Model/Resource/Eav/Mysql4/Category.php | 1062 +- .../Mysql4/Category/Attribute/Collection.php | 57 + .../Eav/Mysql4/Category/Collection.php | 12 +- .../Resource/Eav/Mysql4/Category/Flat.php | 276 +- .../Eav/Mysql4/Category/Flat/Collection.php | 14 + .../Eav/Mysql4/Category/Indexer/Product.php | 509 + .../Resource/Eav/Mysql4/Category/Tree.php | 13 +- .../Eav/Mysql4/Collection/Abstract.php | 4 +- .../Model/Resource/Eav/Mysql4/Config.php | 60 +- .../Eav/Mysql4/Layer/Filter/Attribute.php | 109 + .../Eav/Mysql4/Layer/Filter/Price.php | 191 + .../Model/Resource/Eav/Mysql4/Product.php | 31 +- .../Resource/Eav/Mysql4/Product/Action.php | 86 + .../Product/Attribute/Backend/Tierprice.php | 139 +- .../Mysql4/Product/Attribute/Collection.php | 124 + .../Eav/Mysql4/Product/Collection.php | 163 +- .../Product/Compare/Item/Collection.php | 16 +- .../Eav/Mysql4/Product/Flat/Indexer.php | 110 +- .../Eav/Mysql4/Product/Indexer/Abstract.php | 198 + .../Eav/Mysql4/Product/Indexer/Eav.php | 434 + .../Eav/Mysql4/Product/Indexer/Price.php | 452 + .../Product/Indexer/Price/Configurable.php | 226 + .../Mysql4/Product/Indexer/Price/Default.php | 611 + .../Mysql4/Product/Indexer/Price/Grouped.php | 122 + .../Product/Indexer/Price/Interface.php | 59 + .../Resource/Eav/Mysql4/Product/Link.php | 74 +- .../Resource/Eav/Mysql4/Product/Relation.php | 84 + .../Resource/Eav/Mysql4/Product/Status.php | 8 +- .../Eav/Mysql4/Product/Type/Configurable.php | 57 +- .../Product/Type/Configurable/Attribute.php | 218 +- .../Configurable/Attribute/Collection.php | 12 +- .../Resource/Eav/Mysql4/Product/Website.php | 38 +- .../Model/Resource/Eav/Mysql4/Setup.php | 177 +- .../Catalog/Model/Resource/Eav/Mysql4/Url.php | 62 +- app/code/core/Mage/Catalog/Model/Url.php | 100 +- app/code/core/Mage/Catalog/etc/adminhtml.xml | 117 + app/code/core/Mage/Catalog/etc/config.xml | 881 +- app/code/core/Mage/Catalog/etc/widget.xml | 110 + app/code/core/Mage/Catalog/etc/wsdl.xml | 25 +- .../mysql4-install-1.4.0.0.0.php | 800 + .../mysql4-upgrade-0.7.72-0.7.73.php | 50 + .../mysql4-upgrade-0.7.73-1.4.0.0.0.php | 95 + .../mysql4-upgrade-1.4.0.0.0-1.4.0.0.1.php | 38 + .../mysql4-upgrade-1.4.0.0.1-1.4.0.0.2.php | 38 + .../mysql4-upgrade-1.4.0.0.2-1.4.0.0.3.php | 50 + .../mysql4-upgrade-1.4.0.0.3-1.4.0.0.4.php | 36 + .../mysql4-upgrade-1.4.0.0.4-1.4.0.0.5.php | 73 + .../mysql4-upgrade-1.4.0.0.5-1.4.0.0.6.php | 52 + .../mysql4-upgrade-1.4.0.0.6-1.4.0.0.7.php | 33 + .../mysql4-upgrade-1.4.0.0.7-1.4.0.0.8.php | 34 + .../mysql4-upgrade-1.4.0.0.8-1.4.0.0.9.php | 36 + .../mysql4-upgrade-1.4.0.0.9-1.4.0.0.10.php | 51 + .../core/Mage/CatalogIndex/Model/Indexer.php | 5 +- .../Mage/CatalogIndex/Model/Indexer/Eav.php | 2 +- .../Model/Mysql4/Data/Abstract.php | 10 +- .../Model/Mysql4/Indexer/Abstract.php | 4 +- .../core/Mage/CatalogIndex/Model/Observer.php | 7 +- .../core/Mage/CatalogIndex/etc/config.xml | 309 +- .../Mage/CatalogInventory/Helper/Data.php | 12 + .../CatalogInventory/Model/Indexer/Stock.php | 325 + .../Model/Mysql4/Indexer/Stock.php | 373 + .../Mysql4/Indexer/Stock/Configurable.php | 120 + .../Model/Mysql4/Indexer/Stock/Default.php | 192 + .../Model/Mysql4/Indexer/Stock/Grouped.php | 121 + .../Model/Mysql4/Indexer/Stock/Interface.php | 67 + .../Model/Mysql4/Stock/Status.php | 20 + .../Mage/CatalogInventory/Model/Observer.php | 18 + .../Mage/CatalogInventory/Model/Stock.php | 334 +- .../CatalogInventory/Model/Stock/Item.php | 38 +- .../CatalogInventory/Model/Stock/Status.php | 19 + .../Mage/CatalogInventory/etc/adminhtml.xml | 48 + .../core/Mage/CatalogInventory/etc/config.xml | 77 +- .../core/Mage/CatalogInventory/etc/system.xml | 9 + .../Mage/CatalogRule/Model/Mysql4/Rule.php | 289 +- .../Model/Mysql4/Rule/Product/Price.php | 65 +- .../core/Mage/CatalogRule/Model/Observer.php | 27 +- app/code/core/Mage/CatalogRule/Model/Rule.php | 100 +- .../Model/Rule/Condition/Product.php | 9 +- .../CatalogRule/Model/Rule/Product/Price.php | 61 +- .../core/Mage/CatalogRule/etc/adminhtml.xml | 60 + app/code/core/Mage/CatalogRule/etc/config.xml | 75 +- .../CatalogSearch/Block/Advanced/Form.php | 4 +- .../core/Mage/CatalogSearch/Block/Result.php | 2 +- .../core/Mage/CatalogSearch/Helper/Data.php | 10 +- .../Mage/CatalogSearch/Model/Advanced.php | 9 +- .../Mage/CatalogSearch/Model/Fulltext.php | 4 +- .../CatalogSearch/Model/Indexer/Fulltext.php | 302 + .../core/Mage/CatalogSearch/Model/Layer.php | 6 +- .../Model/Mysql4/Advanced/Collection.php | 4 +- .../CatalogSearch/Model/Mysql4/Fulltext.php | 14 +- .../Model/Mysql4/Query/Collection.php | 4 +- .../Model/Mysql4/Search/Collection.php | 7 +- .../core/Mage/CatalogSearch/Model/Query.php | 23 +- .../Model/System/Config/Backend/Sitemap.php | 40 + .../controllers/AjaxController.php | 4 + .../core/Mage/CatalogSearch/etc/adminhtml.xml | 54 + .../core/Mage/CatalogSearch/etc/config.xml | 59 +- .../core/Mage/CatalogSearch/etc/system.xml | 1 + .../mysql4-upgrade-0.7.6-0.7.7.php | 36 + app/code/core/Mage/Checkout/Block/Cart.php | 2 +- .../Checkout/Block/Cart/Item/Renderer.php | 4 +- app/code/core/Mage/Checkout/Model/Cart.php | 18 + .../core/Mage/Checkout/Model/Mysql4/Setup.php | 37 + app/code/core/Mage/Checkout/Model/Session.php | 2 + .../Mage/Checkout/Model/Type/Abstract.php | 7 + .../Checkout/Model/Type/Multishipping.php | 3 +- .../core/Mage/Checkout/Model/Type/Onepage.php | 18 +- .../controllers/MultishippingController.php | 2 +- .../controllers/OnepageController.php | 3 + app/code/core/Mage/Checkout/etc/adminhtml.xml | 69 + app/code/core/Mage/Checkout/etc/config.xml | 60 +- .../mysql4-upgrade-0.9.3-0.9.4.php | 702 + app/code/core/Mage/Chronopay/etc/config.xml | 13 - app/code/core/Mage/Cms/Block/Block.php | 28 +- app/code/core/Mage/Cms/Block/Page.php | 32 +- app/code/core/Mage/Cms/Block/Widget/Block.php | 60 + .../core/Mage/Cms/Block/Widget/Interface.php | 80 + .../core/Mage/Cms/Block/Widget/Page/Link.php | 127 + app/code/core/Mage/Cms/Controller/Router.php | 23 + app/code/core/Mage/Cms/Helper/Data.php | 24 + app/code/core/Mage/Cms/Helper/Page.php | 56 +- .../core/Mage/Cms/Helper/Wysiwyg/Images.php | 183 + app/code/core/Mage/Cms/Model/Config.php | 73 + app/code/core/Mage/Cms/Model/Mysql4/Page.php | 65 +- .../Mage/Cms/Model/Mysql4/Page/Collection.php | 41 +- .../core/Mage/Cms/Model/Mysql4/Widget.php | 65 + app/code/core/Mage/Cms/Model/Page.php | 24 + .../core/Mage/Cms/Model/Template/Filter.php | 73 + app/code/core/Mage/Cms/Model/Widget.php | 146 + .../core/Mage/Cms/Model/Wysiwyg/Config.php | 160 + .../Mage/Cms/Model/Wysiwyg/Images/Storage.php | 230 + .../Wysiwyg/Images/Storage/Collection.php | 50 + app/code/core/Mage/Cms/etc/adminhtml.xml | 99 + app/code/core/Mage/Cms/etc/config.xml | 95 +- app/code/core/Mage/Cms/etc/system.xml | 39 + app/code/core/Mage/Cms/etc/widget.xml | 73 + .../mysql4-upgrade-0.7.10-0.7.11.php | 41 + .../cms_setup/mysql4-upgrade-0.7.8-0.7.9.php | 42 + .../cms_setup/mysql4-upgrade-0.7.9-0.7.10.php | 36 + app/code/core/Mage/Compiler/Block/Process.php | 2 +- app/code/core/Mage/Compiler/Model/Process.php | 12 +- .../controllers/ProcessController.php | 3 - app/code/core/Mage/Compiler/etc/adminhtml.xml | 65 + .../core/Mage/Compiler/etc/compilation.xml | 5 +- app/code/core/Mage/Compiler/etc/config.xml | 48 - .../Model/System/Config/Backend/Links.php | 40 + app/code/core/Mage/Contacts/etc/adminhtml.xml | 48 + app/code/core/Mage/Contacts/etc/config.xml | 22 - app/code/core/Mage/Contacts/etc/system.xml | 1 + app/code/core/Mage/Core/Block/Abstract.php | 28 +- app/code/core/Mage/Core/Block/Html/Date.php | 6 +- app/code/core/Mage/Core/Block/Html/Link.php | 97 + app/code/core/Mage/Core/Block/Messages.php | 31 +- app/code/core/Mage/Core/Block/Template.php | 46 +- .../Mage/Core/Controller/Front/Action.php | 16 +- .../Mage/Core/Controller/Request/Http.php | 46 + .../Mage/Core/Controller/Varien/Action.php | 33 +- .../Controller/Varien/Router/Standard.php | 8 +- app/code/core/Mage/Core/Helper/String.php | 40 +- app/code/core/Mage/Core/Model/Abstract.php | 74 +- app/code/core/Mage/Core/Model/App.php | 15 +- app/code/core/Mage/Core/Model/Config.php | 12 +- app/code/core/Mage/Core/Model/Config/Data.php | 16 + .../core/Mage/Core/Model/Config/Element.php | 8 +- .../core/Mage/Core/Model/Config/Options.php | 1 - app/code/core/Mage/Core/Model/Cookie.php | 17 +- .../Mage/Core/Model/Design/Source/Design.php | 48 +- .../core/Mage/Core/Model/Email/Template.php | 12 +- .../Mage/Core/Model/Email/Template/Filter.php | 18 +- app/code/core/Mage/Core/Model/Layout.php | 2 +- .../core/Mage/Core/Model/Layout/Update.php | 197 +- app/code/core/Mage/Core/Model/Locale.php | 37 +- .../core/Mage/Core/Model/Locale/Currency.php | 197 - .../core/Mage/Core/Model/Mysql4/Abstract.php | 40 +- .../Mage/Core/Model/Mysql4/Email/Template.php | 2 +- .../core/Mage/Core/Model/Mysql4/Layout.php | 22 +- .../core/Mage/Core/Model/Mysql4/Session.php | 15 +- .../Core/Model/Mysql4/Store/Collection.php | 14 +- .../Mage/Core/Model/Mysql4/Url/Rewrite.php | 27 +- .../Core/Model/Mysql4/Website/Collection.php | 57 +- app/code/core/Mage/Core/Model/Resource.php | 19 +- .../core/Mage/Core/Model/Resource/Setup.php | 9 +- .../Core/Model/Session/Abstract/Varien.php | 44 +- app/code/core/Mage/Core/Model/Store.php | 26 +- app/code/core/Mage/Core/Model/Store/Group.php | 1 + .../core/Mage/Core/Model/Translate/Inline.php | 1 - app/code/core/Mage/Core/Model/Url/Rewrite.php | 23 + app/code/core/Mage/Core/Model/Website.php | 1 + app/code/core/Mage/Core/etc/system.xml | 42 +- app/code/core/Mage/Core/functions.php | 10 +- app/code/core/Mage/Cron/etc/config.xml | 14 - .../Mage/Customer/Block/Widget/Gender.php | 74 + .../core/Mage/Customer/Model/Attribute.php | 56 + .../Model/Convert/Adapter/Customer.php | 16 +- .../Model/Convert/Parser/Customer.php | 16 +- .../core/Mage/Customer/Model/Customer.php | 8 +- .../Entity/Address/Attribute/Collection.php | 69 + .../Mage/Customer/Model/Entity/Attribute.php | 53 + .../Model/Entity/Attribute/Collection.php | 69 + .../Model/Entity/Customer/Collection.php | 20 +- .../core/Mage/Customer/Model/Entity/Setup.php | 28 +- app/code/core/Mage/Customer/Model/Group.php | 221 +- .../controllers/AccountController.php | 10 +- app/code/core/Mage/Customer/etc/adminhtml.xml | 91 + app/code/core/Mage/Customer/etc/config.xml | 83 +- app/code/core/Mage/Customer/etc/system.xml | 9 + .../mysql4-install-1.4.0.0.0.php | 640 + .../mysql4-upgrade-0.8.11-0.8.12.php | 392 + .../mysql4-upgrade-0.8.12-1.4.0.0.0.php | 78 + .../mysql4-upgrade-1.4.0.0.0-1.4.0.0.1.php | 67 + .../mysql4-upgrade-1.4.0.0.1-1.4.0.0.2.php | 35 + app/code/core/Mage/Cybermut/etc/config.xml | 13 - app/code/core/Mage/Cybersource/etc/config.xml | 13 - .../Model/Mysql4/Profile/Collection.php | 2 +- app/code/core/Mage/Dataflow/etc/config.xml | 13 - app/code/core/Mage/Directory/Helper/Data.php | 27 +- .../Mage/Directory/Model/Currency/Filter.php | 2 +- app/code/core/Mage/Directory/etc/config.xml | 7 - app/code/core/Mage/Directory/etc/system.xml | 26 +- .../core/Mage/Downloadable/Helper/File.php | 626 +- .../Model/Mysql4/Indexer/Price.php | 171 + .../Mage/Downloadable/Model/Product/Type.php | 35 +- .../core/Mage/Downloadable/etc/adminhtml.xml | 48 + .../core/Mage/Downloadable/etc/config.xml | 642 +- .../mysql4-upgrade-0.1.15-0.1.16.php | 70 + .../Eav/Block/Adminhtml/Attribute/Edit/Js.php | 41 + .../Attribute/Edit/Main/Abstract.php | 219 + .../Attribute/Edit/Options/Abstract.php | 224 + .../Adminhtml/Attribute/Grid/Abstract.php | 103 + app/code/core/Mage/Eav/Helper/Data.php | 98 + .../System/Config/Source/Inputtype.php | 41 + app/code/core/Mage/Eav/Model/Config.php | 9 +- .../Mage/Eav/Model/Convert/Adapter/Entity.php | 4 +- .../core/Mage/Eav/Model/Entity/Abstract.php | 76 +- .../core/Mage/Eav/Model/Entity/Attribute.php | 40 +- .../Eav/Model/Entity/Attribute/Abstract.php | 6 + .../Entity/Attribute/Backend/Datetime.php | 11 +- .../Model/Entity/Attribute/Source/Table.php | 2 +- .../Eav/Model/Entity/Collection/Abstract.php | 19 +- app/code/core/Mage/Eav/Model/Entity/Setup.php | 219 +- app/code/core/Mage/Eav/Model/Entity/Type.php | 13 + app/code/core/Mage/Eav/Model/Form/Element.php | 105 + .../core/Mage/Eav/Model/Form/Fieldset.php | 143 + app/code/core/Mage/Eav/Model/Form/Type.php | 158 + .../Eav/Model/Mysql4/Entity/Attribute.php | 144 +- .../Mysql4/Entity/Attribute/Collection.php | 102 +- .../Model/Mysql4/Entity/Attribute/Group.php | 15 + .../Model/Mysql4/Entity/Attribute/Option.php | 2 +- .../Entity/Attribute/Option/Collection.php | 2 +- .../Eav/Model/Mysql4/Entity/Attribute/Set.php | 1 + .../Mage/Eav/Model/Mysql4/Entity/Type.php | 14 + .../Mage/Eav/Model/Mysql4/Form/Element.php | 68 + .../Model/Mysql4/Form/Element/Collection.php | 138 + .../Mage/Eav/Model/Mysql4/Form/Fieldset.php | 161 + .../Model/Mysql4/Form/Fieldset/Collection.php | 129 + .../core/Mage/Eav/Model/Mysql4/Form/Type.php | 147 + .../Eav/Model/Mysql4/Form/Type/Collection.php | 76 + app/code/core/Mage/Eav/etc/config.xml | 91 +- .../mysql4-upgrade-0.7.13-0.7.14.php | 48 + .../mysql4-upgrade-0.7.14-0.7.15.php | 97 + app/code/core/Mage/Eway/etc/config.xml | 13 - app/code/core/Mage/Flo2Cash/etc/config.xml | 13 - app/code/core/Mage/GiftMessage/etc/config.xml | 13 - .../core/Mage/GiftRegistry/etc/config.xml | 13 - .../Mage/GoogleAnalytics/Model/Observer.php | 15 +- .../Mage/GoogleAnalytics/etc/adminhtml.xml | 48 + .../core/Mage/GoogleAnalytics/etc/config.xml | 30 +- .../Mage/GoogleBase/Model/Service/Item.php | 1 + .../core/Mage/GoogleBase/etc/adminhtml.xml | 74 + app/code/core/Mage/GoogleBase/etc/config.xml | 60 - .../GoogleCheckout/Model/Mysql4/Setup.php | 2 +- .../Mage/GoogleCheckout/Model/Payment.php | 11 - .../Mage/GoogleCheckout/etc/adminhtml.xml | 48 + .../core/Mage/GoogleCheckout/etc/config.xml | 40 +- .../Block/Adminhtml/Cms/Page/Edit/Enable.php | 80 + .../Cms/Page/Edit/Tab/Googleoptimizer.php | 79 +- .../core/Mage/GoogleOptimizer/etc/config.xml | 29 - app/code/core/Mage/Ideal/etc/config.xml | 14 - .../Index/Block/Adminhtml/Notifications.php | 55 + .../Mage/Index/Block/Adminhtml/Process.php | 37 + .../Index/Block/Adminhtml/Process/Edit.php | 81 + .../Block/Adminhtml/Process/Edit/Form.php | 41 + .../Block/Adminhtml/Process/Edit/Tab/Main.php | 118 + .../Block/Adminhtml/Process/Edit/Tabs.php | 37 + .../Index/Block/Adminhtml/Process/Grid.php | 213 + app/code/core/Mage/Index/Helper/Data.php | 30 + app/code/core/Mage/Index/Model/Event.php | 288 + app/code/core/Mage/Index/Model/Indexer.php | 214 + .../Mage/Index/Model/Indexer/Abstract.php | 148 + .../core/Mage/Index/Model/Mysql4/Abstract.php | 167 + .../core/Mage/Index/Model/Mysql4/Event.php | 87 + .../Index/Model/Mysql4/Event/Collection.php | 122 + .../core/Mage/Index/Model/Mysql4/Process.php | 110 + .../Index/Model/Mysql4/Process/Collection.php | 32 + .../core/Mage/Index/Model/Mysql4/Setup.php | 61 + app/code/core/Mage/Index/Model/Observer.php | 146 + app/code/core/Mage/Index/Model/Process.php | 390 + .../Adminhtml/ProcessController.php | 217 + app/code/core/Mage/Index/etc/adminhtml.xml | 55 + app/code/core/Mage/Index/etc/config.xml | 145 + .../index_setup/mysql4-install-1.4.0.0.php | 63 + .../mysql4-upgrade-1.4.0.0-1.4.0.1.php | 33 + .../mysql4-upgrade-1.4.0.1-1.4.0.2.php | 34 + app/code/core/Mage/Install/etc/config.xml | 11 - app/code/core/Mage/Log/Model/Mysql4/Log.php | 30 + .../core/Mage/Log/Model/Mysql4/Visitor.php | 30 + app/code/core/Mage/Log/Model/Visitor.php | 11 +- app/code/core/Mage/Log/etc/config.xml | 19 - .../core/Mage/Newsletter/Block/Subscribe.php | 13 +- .../Model/Mysql4/Queue/Collection.php | 129 +- .../core/Mage/Newsletter/Model/Subscriber.php | 33 +- .../core/Mage/Newsletter/Model/Template.php | 2 +- .../core/Mage/Newsletter/etc/adminhtml.xml | 94 + app/code/core/Mage/Newsletter/etc/config.xml | 84 +- app/code/core/Mage/Newsletter/etc/system.xml | 19 - .../mysql4-upgrade-0.8.0-0.8.1.php | 44 + app/code/core/Mage/Ogone/Block/Form.php | 39 + app/code/core/Mage/Ogone/Block/Info.php | 41 + app/code/core/Mage/Ogone/Block/Paypage.php | 41 + app/code/core/Mage/Ogone/Block/Placeform.php | 90 + app/code/core/Mage/Ogone/Helper/Data.php | 68 + app/code/core/Mage/Ogone/Model/Api.php | 257 + app/code/core/Mage/Ogone/Model/Api/Debug.php | 40 + app/code/core/Mage/Ogone/Model/Config.php | 151 + .../Mage/Ogone/Model/Mysql4/Api/Debug.php | 40 + .../Mage/Ogone/Model/Source/PaymentAction.php | 45 + .../core/Mage/Ogone/Model/Source/Pmlist.php | 45 + .../core/Mage/Ogone/Model/Source/Template.php | 44 + .../Mage/Ogone/controllers/ApiController.php | 474 + app/code/core/Mage/Ogone/etc/config.xml | 112 + app/code/core/Mage/Ogone/etc/system.xml | 236 + .../sql/ogone_setup/mysql4-install-0.0.1.php | 46 + .../Oscommerce/Block/Adminhtml/Order/View.php | 11 +- .../core/Mage/Oscommerce/etc/adminhtml.xml | 80 + app/code/core/Mage/Oscommerce/etc/config.xml | 65 - app/code/core/Mage/Page/Block/Html/Head.php | 160 +- .../core/Mage/Page/Model/Source/Layout.php | 6 +- app/code/core/Mage/Paybox/etc/config.xml | 13 - .../core/Mage/Paygate/Model/Authorizenet.php | 7 + app/code/core/Mage/Paygate/etc/config.xml | 23 +- app/code/core/Mage/Paygate/etc/system.xml | 3 +- .../mysql4-upgrade-0.7.0-0.7.1.php | 44 + app/code/core/Mage/Payment/Helper/Data.php | 43 +- .../Mage/Payment/Model/Method/Abstract.php | 17 +- .../core/Mage/Payment/Model/Method/Cc.php | 18 +- .../core/Mage/Payment/Model/Method/Free.php | 14 +- app/code/core/Mage/Payment/etc/adminhtml.xml | 48 + app/code/core/Mage/Payment/etc/config.xml | 23 - .../core/Mage/Paypal/Block/Link/Shortcut.php | 16 +- app/code/core/Mage/Paypal/Model/Standard.php | 2 +- app/code/core/Mage/Paypal/etc/adminhtml.xml | 48 + app/code/core/Mage/Paypal/etc/config.xml | 46 +- app/code/core/Mage/PaypalUk/etc/config.xml | 27 +- app/code/core/Mage/Poll/Block/ActivePoll.php | 2 +- .../Poll/Model/Mysql4/Poll/Collection.php | 2 +- app/code/core/Mage/Poll/etc/config.xml | 13 - .../core/Mage/ProductAlert/Model/Observer.php | 5 +- .../core/Mage/ProductAlert/etc/config.xml | 13 - .../core/Mage/ProductAlert/etc/system.xml | 8 +- app/code/core/Mage/Protx/etc/config.xml | 13 - app/code/core/Mage/Rating/etc/adminhtml.xml | 48 + app/code/core/Mage/Rating/etc/config.xml | 32 - .../Mage/Reports/Block/Product/Abstract.php | 4 +- .../Reports/Block/Product/Widget/Compared.php | 39 + .../Reports/Block/Product/Widget/Viewed.php | 39 + .../Reports/Model/Mysql4/Order/Collection.php | 30 +- .../Model/Mysql4/Product/Index/Abstract.php | 2 +- .../Mysql4/Product/Lowstock/Collection.php | 411 +- .../Reports/Model/Mysql4/Quote/Collection.php | 17 +- app/code/core/Mage/Reports/etc/adminhtml.xml | 300 + app/code/core/Mage/Reports/etc/config.xml | 298 +- app/code/core/Mage/Reports/etc/system.xml | 2 +- app/code/core/Mage/Reports/etc/widget.xml | 65 + .../mysql4-upgrade-0.7.8-0.7.9.php | 33 + app/code/core/Mage/Review/Block/Helper.php | 2 +- app/code/core/Mage/Review/Model/Review.php | 8 + app/code/core/Mage/Review/etc/adminhtml.xml | 85 + app/code/core/Mage/Review/etc/config.xml | 79 +- .../core/Mage/Rss/Block/Catalog/Category.php | 81 +- app/code/core/Mage/Rss/Block/Catalog/New.php | 34 +- .../Mage/Rss/Block/Catalog/NotifyStock.php | 11 +- .../core/Mage/Rss/Block/Catalog/Review.php | 11 +- .../core/Mage/Rss/Block/Catalog/Special.php | 79 +- app/code/core/Mage/Rss/Block/Catalog/Tag.php | 24 +- app/code/core/Mage/Rss/Block/Order/New.php | 16 +- app/code/core/Mage/Rss/Helper/Catalog.php | 2 +- app/code/core/Mage/Rss/Helper/Data.php | 4 +- app/code/core/Mage/Rss/Model/Observer.php | 76 + .../Rss/controllers/CatalogController.php | 2 +- .../Mage/Rss/controllers/OrderController.php | 17 +- app/code/core/Mage/Rss/etc/adminhtml.xml | 49 + app/code/core/Mage/Rss/etc/config.xml | 38 +- app/code/core/Mage/Rule/Block/Editable.php | 49 +- app/code/core/Mage/Rule/Block/Newchild.php | 21 +- .../Mage/Rule/Model/Condition/Abstract.php | 69 +- .../Mage/Rule/Model/Condition/Combine.php | 8 +- app/code/core/Mage/Rule/Model/Rule.php | 6 +- .../core/Mage/Sales/Block/Order/Totals.php | 6 +- .../core/Mage/Sales/Block/Reorder/Sidebar.php | 11 + .../Sales/Model/Mysql4/Order/Collection.php | 17 +- .../Model/Mysql4/Order/Invoice/Collection.php | 25 +- .../core/Mage/Sales/Model/Mysql4/Setup.php | 2 + app/code/core/Mage/Sales/Model/Order.php | 21 +- .../Mage/Sales/Model/Order/Creditmemo.php | 4 + .../Model/Order/Creditmemo/Total/Cost.php | 47 + .../core/Mage/Sales/Model/Order/Invoice.php | 3 + .../Sales/Model/Order/Invoice/Total/Cost.php | 47 + .../core/Mage/Sales/Model/Order/Shipment.php | 11 + .../Mage/Sales/Model/Order/Shipment/Track.php | 10 + app/code/core/Mage/Sales/Model/Quote.php | 32 +- .../core/Mage/Sales/Model/Quote/Address.php | 38 +- .../Model/Quote/Address/Total/Abstract.php | 10 +- app/code/core/Mage/Sales/Model/Quote/Item.php | 24 +- app/code/core/Mage/Sales/etc/adminhtml.xml | 127 + app/code/core/Mage/Sales/etc/config.xml | 140 +- .../sql/sales_setup/mysql4-install-0.9.0.php | 20 +- .../mysql4-upgrade-0.9.40-0.9.41.php | 58 + .../mysql4-upgrade-0.9.41-0.9.42.php | 29 + .../mysql4-upgrade-0.9.42-0.9.43.php | 47 + .../mysql4-upgrade-0.9.43-0.9.44.php | 34 + app/code/core/Mage/SalesRule/Helper/Data.php | 34 + app/code/core/Mage/SalesRule/Model/Rule.php | 16 + .../Model/Rule/Condition/Combine.php | 7 + .../Model/Rule/Condition/Product/Found.php | 19 +- .../core/Mage/SalesRule/Model/Validator.php | 18 +- .../core/Mage/SalesRule/etc/adminhtml.xml | 56 + app/code/core/Mage/SalesRule/etc/config.xml | 41 - app/code/core/Mage/Sendfriend/etc/config.xml | 14 - .../Mage/Shipping/Block/Tracking/Popup.php | 96 +- app/code/core/Mage/Shipping/Helper/Data.php | 87 +- app/code/core/Mage/Shipping/Model/Config.php | 32 +- .../controllers/ShippingController.php | 14 +- app/code/core/Mage/Shipping/etc/adminhtml.xml | 53 + app/code/core/Mage/Shipping/etc/config.xml | 37 - .../Sitemap/Model/Mysql4/Catalog/Category.php | 2 +- .../Sitemap/Model/Mysql4/Catalog/Product.php | 2 +- app/code/core/Mage/Sitemap/etc/adminhtml.xml | 66 + app/code/core/Mage/Sitemap/etc/config.xml | 50 - .../core/Mage/Strikeiron/etc/adminhtml.xml | 49 + app/code/core/Mage/Strikeiron/etc/config.xml | 36 - .../core/Mage/Tag/Block/Customer/Tags.php | 3 +- app/code/core/Mage/Tag/Block/Product/List.php | 2 +- .../Tag/Model/Mysql4/Customer/Collection.php | 73 +- .../Tag/Model/Mysql4/Popular/Collection.php | 21 +- .../Tag/Model/Mysql4/Product/Collection.php | 82 +- app/code/core/Mage/Tag/Model/Mysql4/Tag.php | 215 +- .../Mage/Tag/Model/Mysql4/Tag/Collection.php | 162 +- .../Mage/Tag/Model/Mysql4/Tag/Relation.php | 56 +- app/code/core/Mage/Tag/Model/Tag.php | 14 + app/code/core/Mage/Tag/Model/Tag/Relation.php | 17 + .../Tag/controllers/CustomerController.php | 120 +- app/code/core/Mage/Tag/etc/adminhtml.xml | 75 + app/code/core/Mage/Tag/etc/config.xml | 74 +- .../tag_setup/mysql4-upgrade-0.7.2-0.7.3.php | 41 + app/code/core/Mage/Tax/Helper/Data.php | 45 +- .../core/Mage/Tax/Model/Calculation/Rate.php | 23 +- .../Mage/Tax/Model/Mysql4/Calculation.php | 68 +- app/code/core/Mage/Tax/Model/Observer.php | 5 +- .../Tax/Model/Sales/Total/Quote/Subtotal.php | 5 +- .../Mage/Tax/Model/Sales/Total/Quote/Tax.php | 71 +- app/code/core/Mage/Tax/etc/adminhtml.xml | 111 + app/code/core/Mage/Tax/etc/config.xml | 101 +- .../tax_setup/mysql4-upgrade-0.7.9-0.7.10.php | 40 + app/code/core/Mage/Usa/etc/config.xml | 12 +- .../usa_setup/mysql4-upgrade-0.7.0-0.7.1.php | 34 + app/code/core/Mage/Weee/Helper/Data.php | 181 +- .../Mage/Weee/Model/Config/Source/Display.php | 25 +- app/code/core/Mage/Weee/Model/Mysql4/Tax.php | 86 +- app/code/core/Mage/Weee/Model/Observer.php | 72 +- app/code/core/Mage/Weee/Model/Tax.php | 66 +- .../core/Mage/Weee/Model/Total/Quote/Weee.php | 352 +- app/code/core/Mage/Weee/etc/config.xml | 40 +- app/code/core/Mage/Weee/etc/system.xml | 2 +- app/code/core/Mage/Wishlist/Helper/Data.php | 19 +- .../Model/Mysql4/Product/Collection.php | 51 +- .../Model/Mysql4/Wishlist/Collection.php | 7 +- .../core/Mage/Wishlist/Model/Observer.php | 1 + .../Wishlist/controllers/IndexController.php | 20 + app/code/core/Mage/Wishlist/etc/adminhtml.xml | 48 + app/code/core/Mage/Wishlist/etc/config.xml | 36 - .../default/default/layout/catalog.xml | 89 +- .../adminhtml/default/default/layout/cms.xml | 106 + .../default/default/layout/customer.xml | 3 + .../default/layout/googleoptimizer.xml | 29 +- .../default/default/layout/index.xml | 45 + .../adminhtml/default/default/layout/main.xml | 7 +- .../default/default/layout/promo.xml | 87 + .../default/default/layout/sales.xml | 3 +- .../adminhtml/default/default/layout/tag.xml | 52 + .../catalog/category/widget/tree.phtml | 191 + .../catalog/product/attribute/set/main.phtml | 11 +- .../template/catalog/product/edit.phtml | 87 - .../product/edit/action/websites.phtml | 4 +- .../catalog/product/edit/price/tier.phtml | 190 +- .../catalog/product/edit/serializer.phtml | 27 + .../catalog/product/edit/super/config.phtml | 4 +- .../default/template/chronopay/pdf/info.phtml | 4 +- .../cms/page/edit/form/renderer/content.phtml | 33 + .../edit.phtml => cms/wysiwyg/content.phtml} | 37 +- .../template/cms/wysiwyg/content/files.phtml | 48 + .../cms/wysiwyg/content/newfolder.phtml | 33 + .../cms/wysiwyg/content/uploader.phtml | 71 + .../default/template/cms/wysiwyg/js.phtml | 262 + .../default/template/cms/wysiwyg/tree.phtml | 76 + .../template/customer/tab/addresses.phtml | 24 +- .../template/cybersource/pdf/info.phtml | 2 - .../default/template/dashboard/index.phtml | 23 + .../default/template/dashboard/totalbar.phtml | 2 +- .../directory/js/optional_zip_countries.phtml | 66 + .../template/eav/attribute/edit/js.phtml | 26 + .../default/template/eway/pdf/info.phtml | 2 - .../default/template/flo2cash/pdf/info.phtml | 2 - .../template/index/notifications.phtml | 34 + .../default/template/media/uploader.phtml | 13 +- .../template/newsletter/template/edit.phtml | 2 + .../template/notification/baseurl.phtml | 35 + .../default/default/template/ogone/info.phtml | 29 + .../default/default/template/page.phtml | 24 +- .../default/default/template/page/head.phtml | 7 +- .../template/paybox/direct/pdf/info.phtml | 2 - .../template/payment/info/pdf/cc.phtml | 4 +- .../template/payment/info/pdf/ccsave.phtml | 4 +- .../template/paypaluk/direct/pdf/info.phtml | 2 - .../default/default/template/popup.phtml | 4 +- .../default/template/report/grid.phtml | 3 +- .../template/report/store/switcher.phtml | 7 +- .../sales/order/create/coupons/form.phtml | 6 + .../template/sales/order/view/info.phtml | 2 +- .../default/template/system/cache/edit.phtml | 2 + .../system/convert/profile/wizard.phtml | 10 +- .../template/system/email/template/edit.phtml | 2 + .../default/template/tag/edit/container.phtml | 59 + .../template/tag/edit/serializer.phtml | 33 + .../default/default/template/tag/index.phtml | 8 +- .../form/renderer/fieldset/element.phtml | 3 +- .../default/template/widget/grid.phtml | 2 +- .../template/widget/grid/massaction.phtml | 4 +- .../template/widget/grid/serializer.phtml | 37 + .../default/blank/layout/checkout.xml | 3 + .../default/blank/layout/customer.xml | 8 +- .../frontend/default/blank/layout/ogone.xml | 47 + .../template/amazonpayments/cba/success.phtml | 2 +- .../blank/template/callouts/right_col.phtml | 2 +- .../template/catalog/navigation/top.phtml | 6 +- .../catalog/product/compare/list.phtml | 8 +- .../catalog/product/compare/sidebar.phtml | 2 +- .../blank/template/catalog/product/list.phtml | 4 +- .../blank/template/catalog/product/new.phtml | 2 +- .../catalog/product/view/addtocart.phtml | 2 +- .../catalogsearch/advanced/form.phtml | 3 +- .../template/catalogsearch/form.mini.phtml | 2 +- .../blank/template/checkout/cart.phtml | 4 +- .../blank/template/checkout/cart/coupon.phtml | 9 +- .../template/checkout/cart/crosssell.phtml | 2 +- .../template/checkout/cart/shipping.phtml | 4 +- .../template/checkout/cart/sidebar.phtml | 2 +- .../multishipping/address/select.phtml | 2 +- .../checkout/multishipping/addresses.phtml | 6 +- .../checkout/multishipping/billing.phtml | 2 +- .../checkout/multishipping/overview.phtml | 2 +- .../checkout/multishipping/shipping.phtml | 2 +- .../checkout/multishipping/success.phtml | 2 +- .../template/checkout/onepage/billing.phtml | 12 +- .../template/checkout/onepage/link.phtml | 2 +- .../template/checkout/onepage/login.phtml | 4 +- .../template/checkout/onepage/payment.phtml | 2 +- .../template/checkout/onepage/review.phtml | 2 +- .../template/checkout/onepage/shipping.phtml | 4 +- .../checkout/onepage/shipping_method.phtml | 2 +- .../blank/template/checkout/success.phtml | 2 +- .../blank/template/chronopay/info.phtml | 3 +- .../blank/template/contacts/form.phtml | 2 +- .../blank/template/customer/address.phtml | 2 +- .../template/customer/address/book.phtml | 2 +- .../template/customer/address/edit.phtml | 4 +- .../template/customer/form/address.phtml | 6 +- .../customer/form/changepassword.phtml | 2 +- .../template/customer/form/confirmation.phtml | 2 +- .../blank/template/customer/form/edit.phtml | 6 +- .../customer/form/forgotpassword.phtml | 2 +- .../blank/template/customer/form/login.phtml | 4 +- .../template/customer/form/newsletter.phtml | 2 +- .../template/customer/form/register.phtml | 8 +- .../template/customer/widget/gender.phtml | 34 + .../blank/template/cybersource/form.phtml | 61 +- .../blank/template/cybersource/info.phtml | 1 - .../default/blank/template/eway/info.phtml | 3 +- .../blank/template/flo2cash/info.phtml | 3 +- .../blank/template/giftmessage/form.phtml | 18 +- .../blank/template/giftmessage/helper.phtml | 8 +- .../blank/template/giftmessage/inline.phtml | 8 +- .../blank/template/newsletter/subscribe.phtml | 4 +- .../default/blank/template/ogone/form.phtml | 35 + .../default/blank/template/ogone/info.phtml | 27 + .../blank/template/ogone/paypage.phtml | 28 + .../blank/template/ogone/placeform.phtml | 42 + .../blank/template/page/2columns-right.phtml | 2 +- .../default/blank/template/page/print.phtml | 2 +- .../blank/template/paybox/direct/info.phtml | 3 +- .../blank/template/payment/info/cc.phtml | 3 +- .../blank/template/payment/info/ccsave.phtml | 3 +- .../template/paypal/express/review.phtml | 4 +- .../blank/template/paypaluk/direct/form.phtml | 41 +- .../blank/template/paypaluk/direct/info.phtml | 1 - .../template/paypaluk/express/review.phtml | 6 +- .../default/blank/template/poll/active.phtml | 2 +- .../reports/home_product_compared.phtml | 2 +- .../reports/home_product_viewed.phtml | 2 +- .../reports/product/widget/compared.phtml | 67 + .../reports/product/widget/viewed.phtml | 70 + .../blank/template/review/customer/view.phtml | 61 +- .../default/blank/template/review/form.phtml | 2 +- .../blank/template/sales/order/details.phtml | 2 +- .../template/sales/order/shipment/items.phtml | 6 +- .../blank/template/sales/order/view.phtml | 2 +- .../template/sales/reorder/sidebar.phtml | 4 +- .../blank/template/sendfriend/send.phtml | 4 +- .../template/shipping/tracking/popup.phtml | 4 +- .../blank/template/tag/customer/edit.phtml | 2 +- .../blank/template/tag/customer/view.phtml | 6 +- .../default/blank/template/tag/list.phtml | 2 +- .../blank/template/tag/product/result.phtml | 2 +- .../blank/template/wishlist/shared.phtml | 2 +- .../blank/template/wishlist/sharing.phtml | 2 +- .../blank/template/wishlist/view.phtml | 8 +- .../default/default/layout/checkout.xml | 3 + .../default/default/layout/customer.xml | 6 + .../frontend/default/default/layout/ogone.xml | 47 + .../default/template/callouts/right_col.phtml | 2 +- .../template/catalog/layer/filter.phtml | 3 + .../template/catalog/navigation/top.phtml | 14 +- .../catalog/product/compare/list.phtml | 2 +- .../catalogsearch/advanced/form.phtml | 1 - .../template/checkout/onepage/billing.phtml | 10 +- .../template/checkout/onepage/shipping.phtml | 2 +- .../default/template/chronopay/info.phtml | 3 +- .../template/customer/address/edit.phtml | 2 +- .../template/customer/form/address.phtml | 4 +- .../default/template/customer/form/edit.phtml | 4 + .../template/customer/form/register.phtml | 8 +- .../template/customer/widget/gender.phtml | 34 + .../default/template/cybersource/form.phtml | 63 +- .../default/template/cybersource/info.phtml | 3 +- .../directory/js/optional_zip_countries.phtml | 39 + .../default/default/template/eway/info.phtml | 3 +- .../default/template/flo2cash/info.phtml | 3 +- .../default/template/giftmessage/inline.phtml | 6 +- .../template/newsletter/subscribe.phtml | 2 +- .../default/default/template/ogone/form.phtml | 35 + .../default/default/template/ogone/info.phtml | 27 + .../default/template/ogone/paypage.phtml | 28 + .../default/template/ogone/placeform.phtml | 42 + .../template/page/2columns-right.phtml | 2 +- .../default/template/paybox/direct/info.phtml | 3 +- .../default/template/payment/info/cc.phtml | 3 +- .../template/payment/info/ccsave.phtml | 3 +- .../template/paypaluk/direct/form.phtml | 38 +- .../template/paypaluk/direct/info.phtml | 1 - .../reports/product/widget/compared.phtml | 67 + .../reports/product/widget/viewed.phtml | 70 + .../template/sales/order/shipment/items.phtml | 6 +- .../default/template/sales/order/view.phtml | 2 +- .../template/sales/reorder/sidebar.phtml | 2 +- .../default/template/tag/customer/view.phtml | 4 +- .../default/template/wishlist/view.phtml | 2 +- .../default/iphone/layout/checkout.xml | 3 + .../default/iphone/layout/customer.xml | 438 +- .../frontend/default/iphone/layout/ogone.xml | 47 + .../template/catalog/navigation/top.phtml | 84 +- .../catalog/product/compare/list.phtml | 2 +- .../catalogsearch/advanced/form.phtml | 1 - .../template/checkout/onepage/billing.phtml | 14 +- .../template/checkout/onepage/shipping.phtml | 6 +- .../iphone/template/chronopay/info.phtml | 3 +- .../template/customer/address/edit.phtml | 6 +- .../template/customer/form/address.phtml | 4 +- .../iphone/template/customer/form/edit.phtml | 4 + .../template/customer/form/register.phtml | 12 +- .../template/customer/widget/gender.phtml | 34 + .../iphone/template/cybersource/form.phtml | 89 +- .../iphone/template/cybersource/info.phtml | 1 - .../default/iphone/template/eway/info.phtml | 3 +- .../iphone/template/flo2cash/info.phtml | 3 +- .../iphone/template/giftmessage/inline.phtml | 6 +- .../template/newsletter/subscribe.phtml | 2 +- .../default/iphone/template/ogone/form.phtml | 35 + .../default/iphone/template/ogone/info.phtml | 27 + .../iphone/template/ogone/paypage.phtml | 28 + .../iphone/template/ogone/placeform.phtml | 42 + .../iphone/template/paybox/direct/info.phtml | 3 +- .../iphone/template/payment/info/cc.phtml | 3 +- .../iphone/template/payment/info/ccsave.phtml | 3 +- .../template/paypaluk/direct/form.phtml | 39 +- .../template/paypaluk/direct/info.phtml | 1 - .../template/sales/order/shipment.phtml | 10 +- .../iphone/template/sales/order/view.phtml | 2 +- .../template/sales/reorder/sidebar.phtml | 2 +- .../iphone/template/tag/customer/view.phtml | 9 +- .../iphone/template/wishlist/view.phtml | 2 +- .../default/modern/layout/checkout.xml | 3 + .../default/modern/layout/customer.xml | 6 + .../frontend/default/modern/layout/ogone.xml | 47 + .../modern/template/callouts/right_col.phtml | 2 +- .../template/catalog/navigation/top.phtml | 14 +- .../catalog/product/compare/list.phtml | 2 +- .../catalogsearch/advanced/form.phtml | 1 - .../template/checkout/onepage/billing.phtml | 9 +- .../template/checkout/onepage/shipping.phtml | 2 +- .../modern/template/chronopay/info.phtml | 3 +- .../template/customer/address/edit.phtml | 2 +- .../template/customer/form/address.phtml | 4 +- .../modern/template/customer/form/edit.phtml | 4 + .../template/customer/form/register.phtml | 8 +- .../template/customer/widget/gender.phtml | 34 + .../modern/template/cybersource/form.phtml | 87 +- .../modern/template/cybersource/info.phtml | 1 - .../default/modern/template/eway/info.phtml | 3 +- .../modern/template/flo2cash/info.phtml | 3 +- .../modern/template/giftmessage/inline.phtml | 6 +- .../template/newsletter/subscribe.phtml | 2 +- .../default/modern/template/ogone/form.phtml | 35 + .../default/modern/template/ogone/info.phtml | 27 + .../modern/template/ogone/paypage.phtml | 28 + .../modern/template/ogone/placeform.phtml | 42 + .../modern/template/paybox/direct/info.phtml | 3 +- .../modern/template/payment/form/cc.phtml | 26 +- .../modern/template/payment/info/cc.phtml | 3 +- .../modern/template/payment/info/ccsave.phtml | 3 +- .../template/paypaluk/direct/form.phtml | 39 +- .../template/paypaluk/direct/info.phtml | 1 - .../reports/product/widget/compared.phtml | 67 + .../reports/product/widget/viewed.phtml | 70 + .../template/sales/order/shipment/items.phtml | 6 +- .../modern/template/sales/order/view.phtml | 2 +- .../template/sales/reorder/sidebar.phtml | 2 +- .../modern/template/tag/customer/view.phtml | 4 +- .../modern/template/wishlist/view.phtml | 3 +- app/etc/config.xml | 7 + app/etc/modules/Mage_All.xml | 9 + app/etc/modules/Mage_Ogone.xml | 39 + index.php | 10 +- js/mage/adminhtml/form.js | 4 + js/mage/adminhtml/grid.js | 167 +- js/mage/adminhtml/product.js | 29 +- js/mage/adminhtml/rules.js | 28 +- js/mage/adminhtml/tools.js | 160 + .../plugins/magentowidget/editor_plugin.js | 90 + .../magentowidget/editor_plugin_src.js | 90 + .../plugins/magentowidget/img/icon.gif | Bin 0 -> 572 bytes js/mage/adminhtml/wysiwyg/tiny_mce/setup.js | 222 + .../themes/advanced/skins/default/content.css | 32 + .../themes/advanced/skins/default/dialog.css | 62 + js/mage/adminhtml/wysiwyg/widget.js | 334 + js/prototype/validation.js | 41 +- js/scriptaculous/controls.js | 2 +- js/tiny_mce/blank.htm | 9 + js/tiny_mce/classes/AddOnManager.js | 95 + js/tiny_mce/classes/CommandManager.js | 54 + js/tiny_mce/classes/ControlManager.js | 489 + js/tiny_mce/classes/Developer.js | 91 + js/tiny_mce/classes/Editor.js | 2295 +++ js/tiny_mce/classes/EditorCommands.js | 934 ++ js/tiny_mce/classes/EditorManager.js | 453 + js/tiny_mce/classes/ForceBlocks.js | 644 + js/tiny_mce/classes/Popup.js | 412 + js/tiny_mce/classes/UndoManager.js | 183 + js/tiny_mce/classes/WindowManager.js | 169 + js/tiny_mce/classes/adapter/jquery/adapter.js | 240 + .../classes/adapter/jquery/jquery.tinymce.js | 179 + .../classes/adapter/prototype/adapter.js | 38 + js/tiny_mce/classes/commands/BlockQuote.js | 135 + js/tiny_mce/classes/commands/CutCopyPaste.js | 24 + .../classes/commands/InsertHorizontalRule.js | 15 + js/tiny_mce/classes/commands/RemoveFormat.js | 173 + js/tiny_mce/classes/commands/UndoRedo.js | 38 + js/tiny_mce/classes/dom/DOMUtils.js | 1823 +++ js/tiny_mce/classes/dom/Element.js | 199 + js/tiny_mce/classes/dom/EventUtils.js | 349 + js/tiny_mce/classes/dom/Range.js | 721 + js/tiny_mce/classes/dom/ScriptLoader.js | 351 + js/tiny_mce/classes/dom/Selection.js | 747 + js/tiny_mce/classes/dom/Serializer.js | 979 ++ js/tiny_mce/classes/dom/Sizzle.js | 975 ++ js/tiny_mce/classes/dom/StringWriter.js | 200 + js/tiny_mce/classes/dom/TridentSelection.js | 273 + js/tiny_mce/classes/dom/XMLWriter.js | 155 + js/tiny_mce/classes/firebug/firebug-lite.js | 2518 +++ js/tiny_mce/classes/tinymce.js | 549 + js/tiny_mce/classes/ui/Button.js | 68 + js/tiny_mce/classes/ui/ColorSplitButton.js | 212 + js/tiny_mce/classes/ui/Container.js | 56 + js/tiny_mce/classes/ui/Control.js | 182 + js/tiny_mce/classes/ui/DropMenu.js | 397 + js/tiny_mce/classes/ui/ListBox.js | 320 + js/tiny_mce/classes/ui/Menu.js | 175 + js/tiny_mce/classes/ui/MenuButton.js | 128 + js/tiny_mce/classes/ui/MenuItem.js | 69 + js/tiny_mce/classes/ui/NativeListBox.js | 198 + js/tiny_mce/classes/ui/Separator.js | 34 + js/tiny_mce/classes/ui/SplitButton.js | 99 + js/tiny_mce/classes/ui/Toolbar.js | 86 + js/tiny_mce/classes/util/Cookie.js | 126 + js/tiny_mce/classes/util/Dispatcher.js | 101 + js/tiny_mce/classes/util/JSON.js | 81 + js/tiny_mce/classes/util/JSONP.js | 25 + js/tiny_mce/classes/util/JSONRequest.js | 87 + js/tiny_mce/classes/util/URI.js | 289 + js/tiny_mce/classes/util/XHR.js | 80 + js/tiny_mce/classes/xml/Parser.js | 126 + js/tiny_mce/jquery.tinymce.js | 1 + js/tiny_mce/langs/en.js | 154 + js/tiny_mce/license.txt | 504 + js/tiny_mce/plugins/advhr/css/advhr.css | 5 + js/tiny_mce/plugins/advhr/editor_plugin.js | 1 + .../plugins/advhr/editor_plugin_src.js | 54 + js/tiny_mce/plugins/advhr/js/rule.js | 43 + js/tiny_mce/plugins/advhr/langs/en_dlg.js | 5 + js/tiny_mce/plugins/advhr/rule.htm | 62 + js/tiny_mce/plugins/advimage/css/advimage.css | 13 + js/tiny_mce/plugins/advimage/editor_plugin.js | 1 + .../plugins/advimage/editor_plugin_src.js | 47 + js/tiny_mce/plugins/advimage/image.htm | 237 + js/tiny_mce/plugins/advimage/img/sample.gif | Bin 0 -> 1624 bytes js/tiny_mce/plugins/advimage/js/image.js | 443 + js/tiny_mce/plugins/advimage/langs/en_dlg.js | 43 + js/tiny_mce/plugins/advlink/css/advlink.css | 8 + js/tiny_mce/plugins/advlink/editor_plugin.js | 1 + .../plugins/advlink/editor_plugin_src.js | 58 + js/tiny_mce/plugins/advlink/js/advlink.js | 528 + js/tiny_mce/plugins/advlink/langs/en_dlg.js | 52 + js/tiny_mce/plugins/advlink/link.htm | 338 + .../plugins/autoresize/editor_plugin.js | 1 + .../plugins/autoresize/editor_plugin_src.js | 111 + js/tiny_mce/plugins/autosave/editor_plugin.js | 1 + .../plugins/autosave/editor_plugin_src.js | 51 + js/tiny_mce/plugins/bbcode/editor_plugin.js | 1 + .../plugins/bbcode/editor_plugin_src.js | 117 + js/tiny_mce/plugins/compat2x/editor_plugin.js | 1 + .../plugins/compat2x/editor_plugin_src.js | 616 + .../plugins/contextmenu/editor_plugin.js | 1 + .../plugins/contextmenu/editor_plugin_src.js | 95 + .../plugins/directionality/editor_plugin.js | 1 + .../directionality/editor_plugin_src.js | 79 + js/tiny_mce/plugins/emotions/editor_plugin.js | 1 + .../plugins/emotions/editor_plugin_src.js | 40 + js/tiny_mce/plugins/emotions/emotions.htm | 40 + .../plugins/emotions/img/smiley-cool.gif | Bin 0 -> 354 bytes .../plugins/emotions/img/smiley-cry.gif | Bin 0 -> 329 bytes .../emotions/img/smiley-embarassed.gif | Bin 0 -> 331 bytes .../emotions/img/smiley-foot-in-mouth.gif | Bin 0 -> 344 bytes .../plugins/emotions/img/smiley-frown.gif | Bin 0 -> 340 bytes .../plugins/emotions/img/smiley-innocent.gif | Bin 0 -> 336 bytes .../plugins/emotions/img/smiley-kiss.gif | Bin 0 -> 338 bytes .../plugins/emotions/img/smiley-laughing.gif | Bin 0 -> 344 bytes .../emotions/img/smiley-money-mouth.gif | Bin 0 -> 321 bytes .../plugins/emotions/img/smiley-sealed.gif | Bin 0 -> 325 bytes .../plugins/emotions/img/smiley-smile.gif | Bin 0 -> 345 bytes .../plugins/emotions/img/smiley-surprised.gif | Bin 0 -> 342 bytes .../emotions/img/smiley-tongue-out.gif | Bin 0 -> 328 bytes .../plugins/emotions/img/smiley-undecided.gif | Bin 0 -> 337 bytes .../plugins/emotions/img/smiley-wink.gif | Bin 0 -> 351 bytes .../plugins/emotions/img/smiley-yell.gif | Bin 0 -> 336 bytes js/tiny_mce/plugins/emotions/js/emotions.js | 22 + js/tiny_mce/plugins/emotions/langs/en_dlg.js | 20 + js/tiny_mce/plugins/example/dialog.htm | 27 + js/tiny_mce/plugins/example/editor_plugin.js | 1 + .../plugins/example/editor_plugin_src.js | 81 + js/tiny_mce/plugins/example/img/example.gif | Bin 0 -> 87 bytes js/tiny_mce/plugins/example/js/dialog.js | 19 + js/tiny_mce/plugins/example/langs/en.js | 3 + js/tiny_mce/plugins/example/langs/en_dlg.js | 3 + js/tiny_mce/plugins/fullpage/css/fullpage.css | 182 + js/tiny_mce/plugins/fullpage/editor_plugin.js | 1 + .../plugins/fullpage/editor_plugin_src.js | 146 + js/tiny_mce/plugins/fullpage/fullpage.htm | 576 + js/tiny_mce/plugins/fullpage/js/fullpage.js | 461 + js/tiny_mce/plugins/fullpage/langs/en_dlg.js | 85 + .../plugins/fullscreen/editor_plugin.js | 1 + .../plugins/fullscreen/editor_plugin_src.js | 145 + js/tiny_mce/plugins/fullscreen/fullscreen.htm | 110 + js/tiny_mce/plugins/iespell/editor_plugin.js | 1 + .../plugins/iespell/editor_plugin_src.js | 51 + .../plugins/inlinepopups/editor_plugin.js | 1 + .../plugins/inlinepopups/editor_plugin_src.js | 632 + .../skins/clearlooks2/img/alert.gif | Bin 0 -> 818 bytes .../skins/clearlooks2/img/button.gif | Bin 0 -> 280 bytes .../skins/clearlooks2/img/buttons.gif | Bin 0 -> 1195 bytes .../skins/clearlooks2/img/confirm.gif | Bin 0 -> 915 bytes .../skins/clearlooks2/img/corners.gif | Bin 0 -> 911 bytes .../skins/clearlooks2/img/horizontal.gif | Bin 0 -> 769 bytes .../skins/clearlooks2/img/vertical.gif | Bin 0 -> 92 bytes .../inlinepopups/skins/clearlooks2/window.css | 90 + js/tiny_mce/plugins/inlinepopups/template.htm | 387 + .../plugins/insertdatetime/editor_plugin.js | 1 + .../insertdatetime/editor_plugin_src.js | 80 + js/tiny_mce/plugins/layer/editor_plugin.js | 1 + .../plugins/layer/editor_plugin_src.js | 209 + js/tiny_mce/plugins/media/css/content.css | 6 + js/tiny_mce/plugins/media/css/media.css | 16 + js/tiny_mce/plugins/media/editor_plugin.js | 1 + .../plugins/media/editor_plugin_src.js | 405 + js/tiny_mce/plugins/media/img/flash.gif | Bin 0 -> 241 bytes js/tiny_mce/plugins/media/img/flv_player.swf | Bin 0 -> 11668 bytes js/tiny_mce/plugins/media/img/quicktime.gif | Bin 0 -> 303 bytes js/tiny_mce/plugins/media/img/realmedia.gif | Bin 0 -> 439 bytes js/tiny_mce/plugins/media/img/shockwave.gif | Bin 0 -> 387 bytes js/tiny_mce/plugins/media/img/trans.gif | Bin 0 -> 43 bytes .../plugins/media/img/windowsmedia.gif | Bin 0 -> 415 bytes js/tiny_mce/plugins/media/js/embed.js | 73 + js/tiny_mce/plugins/media/js/media.js | 630 + js/tiny_mce/plugins/media/langs/en_dlg.js | 103 + js/tiny_mce/plugins/media/media.htm | 822 + .../plugins/nonbreaking/editor_plugin.js | 1 + .../plugins/nonbreaking/editor_plugin_src.js | 50 + .../plugins/noneditable/editor_plugin.js | 1 + .../plugins/noneditable/editor_plugin_src.js | 87 + js/tiny_mce/plugins/pagebreak/css/content.css | 1 + .../plugins/pagebreak/editor_plugin.js | 1 + .../plugins/pagebreak/editor_plugin_src.js | 74 + .../plugins/pagebreak/img/pagebreak.gif | Bin 0 -> 325 bytes js/tiny_mce/plugins/pagebreak/img/trans.gif | Bin 0 -> 43 bytes js/tiny_mce/plugins/paste/editor_plugin.js | 1 + .../plugins/paste/editor_plugin_src.js | 512 + js/tiny_mce/plugins/paste/js/pastetext.js | 36 + js/tiny_mce/plugins/paste/js/pasteword.js | 51 + js/tiny_mce/plugins/paste/langs/en_dlg.js | 5 + js/tiny_mce/plugins/paste/pastetext.htm | 33 + js/tiny_mce/plugins/paste/pasteword.htm | 27 + js/tiny_mce/plugins/preview/editor_plugin.js | 1 + .../plugins/preview/editor_plugin_src.js | 50 + js/tiny_mce/plugins/preview/example.html | 28 + js/tiny_mce/plugins/preview/jscripts/embed.js | 73 + js/tiny_mce/plugins/preview/preview.html | 17 + js/tiny_mce/plugins/print/editor_plugin.js | 1 + .../plugins/print/editor_plugin_src.js | 31 + js/tiny_mce/plugins/safari/blank.htm | 1 + js/tiny_mce/plugins/safari/editor_plugin.js | 1 + .../plugins/safari/editor_plugin_src.js | 438 + js/tiny_mce/plugins/save/editor_plugin.js | 1 + js/tiny_mce/plugins/save/editor_plugin_src.js | 98 + .../searchreplace/css/searchreplace.css | 6 + .../plugins/searchreplace/editor_plugin.js | 1 + .../searchreplace/editor_plugin_src.js | 54 + .../plugins/searchreplace/js/searchreplace.js | 126 + .../plugins/searchreplace/langs/en_dlg.js | 16 + .../plugins/searchreplace/searchreplace.htm | 104 + .../plugins/spellchecker/css/content.css | 1 + .../plugins/spellchecker/editor_plugin.js | 1 + .../plugins/spellchecker/editor_plugin_src.js | 338 + .../plugins/spellchecker/img/wline.gif | Bin 0 -> 46 bytes js/tiny_mce/plugins/style/css/props.css | 13 + js/tiny_mce/plugins/style/editor_plugin.js | 1 + .../plugins/style/editor_plugin_src.js | 52 + js/tiny_mce/plugins/style/js/props.js | 641 + js/tiny_mce/plugins/style/langs/en_dlg.js | 63 + js/tiny_mce/plugins/style/props.htm | 730 + js/tiny_mce/plugins/tabfocus/editor_plugin.js | 1 + .../plugins/tabfocus/editor_plugin_src.js | 109 + js/tiny_mce/plugins/table/cell.htm | 183 + js/tiny_mce/plugins/table/css/cell.css | 17 + js/tiny_mce/plugins/table/css/row.css | 25 + js/tiny_mce/plugins/table/css/table.css | 13 + js/tiny_mce/plugins/table/editor_plugin.js | 1 + .../plugins/table/editor_plugin_src.js | 1136 ++ js/tiny_mce/plugins/table/js/cell.js | 269 + js/tiny_mce/plugins/table/js/merge_cells.js | 29 + js/tiny_mce/plugins/table/js/row.js | 212 + js/tiny_mce/plugins/table/js/table.js | 440 + js/tiny_mce/plugins/table/langs/en_dlg.js | 74 + js/tiny_mce/plugins/table/merge_cells.htm | 37 + js/tiny_mce/plugins/table/row.htm | 160 + js/tiny_mce/plugins/table/table.htm | 192 + js/tiny_mce/plugins/template/blank.htm | 12 + js/tiny_mce/plugins/template/css/template.css | 23 + js/tiny_mce/plugins/template/editor_plugin.js | 1 + .../plugins/template/editor_plugin_src.js | 156 + js/tiny_mce/plugins/template/js/template.js | 106 + js/tiny_mce/plugins/template/langs/en_dlg.js | 15 + js/tiny_mce/plugins/template/template.htm | 38 + .../plugins/visualchars/editor_plugin.js | 1 + .../plugins/visualchars/editor_plugin_src.js | 73 + js/tiny_mce/plugins/xhtmlxtras/abbr.htm | 148 + js/tiny_mce/plugins/xhtmlxtras/acronym.htm | 148 + js/tiny_mce/plugins/xhtmlxtras/attributes.htm | 153 + js/tiny_mce/plugins/xhtmlxtras/cite.htm | 148 + .../plugins/xhtmlxtras/css/attributes.css | 11 + js/tiny_mce/plugins/xhtmlxtras/css/popup.css | 9 + js/tiny_mce/plugins/xhtmlxtras/del.htm | 169 + .../plugins/xhtmlxtras/editor_plugin.js | 1 + .../plugins/xhtmlxtras/editor_plugin_src.js | 136 + js/tiny_mce/plugins/xhtmlxtras/ins.htm | 169 + js/tiny_mce/plugins/xhtmlxtras/js/abbr.js | 25 + js/tiny_mce/plugins/xhtmlxtras/js/acronym.js | 25 + .../plugins/xhtmlxtras/js/attributes.js | 123 + js/tiny_mce/plugins/xhtmlxtras/js/cite.js | 25 + js/tiny_mce/plugins/xhtmlxtras/js/del.js | 60 + .../plugins/xhtmlxtras/js/element_common.js | 231 + js/tiny_mce/plugins/xhtmlxtras/js/ins.js | 59 + .../plugins/xhtmlxtras/langs/en_dlg.js | 32 + js/tiny_mce/themes/advanced/about.htm | 56 + js/tiny_mce/themes/advanced/anchor.htm | 31 + js/tiny_mce/themes/advanced/charmap.htm | 53 + js/tiny_mce/themes/advanced/color_picker.htm | 75 + .../themes/advanced/editor_template.js | 1 + .../themes/advanced/editor_template_src.js | 1153 ++ js/tiny_mce/themes/advanced/image.htm | 85 + .../themes/advanced/img/colorpicker.jpg | Bin 0 -> 3189 bytes js/tiny_mce/themes/advanced/img/icons.gif | Bin 0 -> 11505 bytes js/tiny_mce/themes/advanced/js/about.js | 72 + js/tiny_mce/themes/advanced/js/anchor.js | 37 + js/tiny_mce/themes/advanced/js/charmap.js | 325 + .../themes/advanced/js/color_picker.js | 253 + js/tiny_mce/themes/advanced/js/image.js | 245 + js/tiny_mce/themes/advanced/js/link.js | 156 + .../themes/advanced/js/source_editor.js | 62 + js/tiny_mce/themes/advanced/langs/en.js | 62 + js/tiny_mce/themes/advanced/langs/en_dlg.js | 51 + js/tiny_mce/themes/advanced/link.htm | 63 + .../themes/advanced/skins/default/content.css | 32 + .../themes/advanced/skins/default/dialog.css | 116 + .../advanced/skins/default/img/buttons.png | Bin 0 -> 3274 bytes .../advanced/skins/default/img/items.gif | Bin 0 -> 70 bytes .../advanced/skins/default/img/menu_arrow.gif | Bin 0 -> 68 bytes .../advanced/skins/default/img/menu_check.gif | Bin 0 -> 70 bytes .../advanced/skins/default/img/progress.gif | Bin 0 -> 1787 bytes .../advanced/skins/default/img/tabs.gif | Bin 0 -> 1326 bytes .../themes/advanced/skins/default/ui.css | 214 + .../themes/advanced/skins/o2k7/content.css | 32 + .../themes/advanced/skins/o2k7/dialog.css | 115 + .../advanced/skins/o2k7/img/button_bg.png | Bin 0 -> 5859 bytes .../skins/o2k7/img/button_bg_black.png | Bin 0 -> 3736 bytes .../skins/o2k7/img/button_bg_silver.png | Bin 0 -> 5358 bytes js/tiny_mce/themes/advanced/skins/o2k7/ui.css | 215 + .../themes/advanced/skins/o2k7/ui_black.css | 8 + .../themes/advanced/skins/o2k7/ui_silver.css | 5 + js/tiny_mce/themes/advanced/source_editor.htm | 31 + js/tiny_mce/themes/simple/editor_template.js | 1 + .../themes/simple/editor_template_src.js | 85 + js/tiny_mce/themes/simple/img/icons.gif | Bin 0 -> 1440 bytes js/tiny_mce/themes/simple/langs/en.js | 11 + .../themes/simple/skins/default/content.css | 25 + .../themes/simple/skins/default/ui.css | 32 + .../themes/simple/skins/o2k7/content.css | 17 + .../simple/skins/o2k7/img/button_bg.png | Bin 0 -> 5102 bytes js/tiny_mce/themes/simple/skins/o2k7/ui.css | 35 + js/tiny_mce/tiny_mce.js | 1 + js/tiny_mce/tiny_mce_dev.js | 128 + js/tiny_mce/tiny_mce_jquery.js | 1 + js/tiny_mce/tiny_mce_jquery_src.js | 12198 +++++++++++++++ js/tiny_mce/tiny_mce_popup.js | 5 + js/tiny_mce/tiny_mce_prototype.js | 1 + js/tiny_mce/tiny_mce_prototype_src.js | 13057 ++++++++++++++++ js/tiny_mce/tiny_mce_src.js | 13030 +++++++++++++++ js/tiny_mce/utils/editable_selects.js | 69 + js/tiny_mce/utils/form_utils.js | 199 + js/tiny_mce/utils/mctabs.js | 76 + js/tiny_mce/utils/validate.js | 219 + js/varien/form.js | 81 +- lib/Varien/Autoload.php | 8 +- lib/Varien/Convert/Validator/Dryrun.php | 2 +- lib/Varien/Data/Collection/Filesystem.php | 16 +- lib/Varien/Data/Form.php | 11 + lib/Varien/Data/Form/Element/Abstract.php | 9 + lib/Varien/Data/Form/Element/Checkboxes.php | 6 +- lib/Varien/Data/Form/Element/Date.php | 4 +- lib/Varien/Data/Form/Element/Editor.php | 225 +- lib/Varien/Data/Form/Element/Gallery.php | 6 +- lib/Varien/Data/Form/Element/Image.php | 2 +- lib/Varien/Data/Form/Element/Link.php | 68 + lib/Varien/Date.php | 3 +- lib/Varien/Db/Adapter/Pdo/Mysql.php | 60 +- lib/Varien/Db/Select.php | 21 + lib/Varien/Filter/Template.php | 2 +- lib/Varien/Image.php | 34 + lib/Varien/Image/Adapter/Abstract.php | 15 + lib/Varien/Image/Adapter/Gd2.php | 26 +- lib/Varien/Object.php | 7 - lib/Varien/Simplexml/Element.php | 37 +- lib/Zend/Acl.php | 87 +- lib/Zend/Acl/Assert/Interface.php | 6 +- lib/Zend/Acl/Exception.php | 6 +- lib/Zend/Acl/Resource.php | 6 +- lib/Zend/Acl/Resource/Interface.php | 6 +- lib/Zend/Acl/Role.php | 6 +- lib/Zend/Acl/Role/Interface.php | 6 +- lib/Zend/Acl/Role/Registry.php | 6 +- lib/Zend/Acl/Role/Registry/Exception.php | 6 +- lib/Zend/Amf/Adobe/Auth.php | 5 +- lib/Zend/Amf/Adobe/DbInspector.php | 3 +- lib/Zend/Amf/Adobe/Introspector.php | 3 +- lib/Zend/Amf/Auth/Abstract.php | 3 +- lib/Zend/Amf/Constants.php | 1 + lib/Zend/Amf/Exception.php | 1 + lib/Zend/Amf/Parse/Amf0/Deserializer.php | 2 +- lib/Zend/Amf/Parse/Amf0/Serializer.php | 1 + lib/Zend/Amf/Parse/Amf3/Deserializer.php | 5 +- lib/Zend/Amf/Parse/Amf3/Serializer.php | 1 + lib/Zend/Amf/Parse/Deserializer.php | 1 + lib/Zend/Amf/Parse/InputStream.php | 1 + lib/Zend/Amf/Parse/OutputStream.php | 1 + lib/Zend/Amf/Parse/Resource/MysqlResult.php | 11 +- lib/Zend/Amf/Parse/Resource/MysqliResult.php | 7 +- lib/Zend/Amf/Parse/Resource/Stream.php | 7 +- lib/Zend/Amf/Parse/Serializer.php | 1 + lib/Zend/Amf/Parse/TypeLoader.php | 16 + lib/Zend/Amf/Request.php | 1 + lib/Zend/Amf/Request/Http.php | 1 + lib/Zend/Amf/Response.php | 1 + lib/Zend/Amf/Response/Http.php | 1 + lib/Zend/Amf/Server.php | 20 +- lib/Zend/Amf/Server/Exception.php | 16 +- lib/Zend/Amf/Util/BinaryStream.php | 1 + lib/Zend/Amf/Value/ByteArray.php | 1 + lib/Zend/Amf/Value/MessageBody.php | 1 + lib/Zend/Amf/Value/MessageHeader.php | 1 + .../Amf/Value/Messaging/AbstractMessage.php | 1 + .../Value/Messaging/AcknowledgeMessage.php | 1 + .../Amf/Value/Messaging/ArrayCollection.php | 7 +- lib/Zend/Amf/Value/Messaging/AsyncMessage.php | 1 + .../Amf/Value/Messaging/CommandMessage.php | 6 +- lib/Zend/Amf/Value/Messaging/ErrorMessage.php | 1 + .../Amf/Value/Messaging/RemotingMessage.php | 1 + lib/Zend/Amf/Value/TraitsInfo.php | 1 + lib/Zend/Application.php | 34 +- lib/Zend/Application/Bootstrap/Bootstrap.php | 6 +- .../Bootstrap/BootstrapAbstract.php | 84 +- .../Application/Bootstrap/Bootstrapper.php | 6 +- lib/Zend/Application/Bootstrap/Exception.php | 6 +- .../Bootstrap/ResourceBootstrapper.php | 6 +- lib/Zend/Application/Exception.php | 6 +- lib/Zend/Application/Module/Autoloader.php | 8 +- lib/Zend/Application/Module/Bootstrap.php | 10 +- lib/Zend/Application/Resource/Db.php | 6 +- lib/Zend/Application/Resource/Exception.php | 6 +- .../Application/Resource/Frontcontroller.php | 10 +- lib/Zend/Application/Resource/Layout.php | 6 +- lib/Zend/Application/Resource/Locale.php | 6 +- lib/Zend/Application/Resource/Modules.php | 39 +- lib/Zend/Application/Resource/Navigation.php | 22 +- lib/Zend/Application/Resource/Resource.php | 6 +- .../Application/Resource/ResourceAbstract.php | 6 +- lib/Zend/Application/Resource/Router.php | 18 +- lib/Zend/Application/Resource/Session.php | 6 +- lib/Zend/Application/Resource/Translate.php | 6 +- lib/Zend/Application/Resource/View.php | 6 +- lib/Zend/Auth.php | 6 +- lib/Zend/Auth/Adapter/DbTable.php | 6 +- lib/Zend/Auth/Adapter/Digest.php | 6 +- lib/Zend/Auth/Adapter/Exception.php | 6 +- lib/Zend/Auth/Adapter/Http.php | 6 +- .../Auth/Adapter/Http/Resolver/Exception.php | 6 +- lib/Zend/Auth/Adapter/Http/Resolver/File.php | 6 +- .../Auth/Adapter/Http/Resolver/Interface.php | 6 +- lib/Zend/Auth/Adapter/InfoCard.php | 6 +- lib/Zend/Auth/Adapter/Interface.php | 6 +- lib/Zend/Auth/Adapter/Ldap.php | 184 +- lib/Zend/Auth/Adapter/OpenId.php | 6 +- lib/Zend/Auth/Exception.php | 6 +- lib/Zend/Auth/Result.php | 6 +- lib/Zend/Auth/Storage/Exception.php | 6 +- lib/Zend/Auth/Storage/Interface.php | 6 +- lib/Zend/Auth/Storage/NonPersistent.php | 6 +- lib/Zend/Auth/Storage/Session.php | 6 +- lib/Zend/Cache.php | 6 +- lib/Zend/Cache/Backend.php | 5 +- lib/Zend/Cache/Backend/Apc.php | 5 +- lib/Zend/Cache/Backend/ExtendedInterface.php | 5 +- lib/Zend/Cache/Backend/File.php | 60 +- lib/Zend/Cache/Backend/Interface.php | 5 +- lib/Zend/Cache/Backend/Memcached.php | 5 +- lib/Zend/Cache/Backend/Sqlite.php | 5 +- lib/Zend/Cache/Backend/Test.php | 5 +- lib/Zend/Cache/Backend/TwoLevels.php | 19 +- lib/Zend/Cache/Backend/Xcache.php | 5 +- lib/Zend/Cache/Backend/ZendPlatform.php | 5 +- lib/Zend/Cache/Backend/ZendServer.php | 415 +- lib/Zend/Cache/Backend/ZendServer/Disk.php | 15 +- lib/Zend/Cache/Backend/ZendServer/ShMem.php | 15 +- lib/Zend/Cache/Core.php | 25 +- lib/Zend/Cache/Exception.php | 5 +- lib/Zend/Cache/Frontend/Class.php | 5 +- lib/Zend/Cache/Frontend/File.php | 5 +- lib/Zend/Cache/Frontend/Function.php | 5 +- lib/Zend/Cache/Frontend/Output.php | 5 +- lib/Zend/Cache/Frontend/Page.php | 7 +- lib/Zend/Captcha/Adapter.php | 6 +- lib/Zend/Captcha/Base.php | 6 +- lib/Zend/Captcha/Dumb.php | 6 +- lib/Zend/Captcha/Exception.php | 6 +- lib/Zend/Captcha/Figlet.php | 6 +- lib/Zend/Captcha/Image.php | 6 +- lib/Zend/Captcha/ReCaptcha.php | 6 +- lib/Zend/Captcha/Word.php | 6 +- lib/Zend/CodeGenerator/Abstract.php | 2 +- lib/Zend/CodeGenerator/Exception.php | 2 +- lib/Zend/CodeGenerator/Php/Abstract.php | 10 +- lib/Zend/CodeGenerator/Php/Body.php | 2 +- lib/Zend/CodeGenerator/Php/Class.php | 21 +- lib/Zend/CodeGenerator/Php/Docblock.php | 18 +- lib/Zend/CodeGenerator/Php/Docblock/Tag.php | 8 +- .../Php/Docblock/Tag/License.php | 4 +- .../CodeGenerator/Php/Docblock/Tag/Param.php | 7 +- .../CodeGenerator/Php/Docblock/Tag/Return.php | 4 +- lib/Zend/CodeGenerator/Php/Exception.php | 2 +- lib/Zend/CodeGenerator/Php/File.php | 16 +- .../CodeGenerator/Php/Member/Abstract.php | 42 +- .../CodeGenerator/Php/Member/Container.php | 2 +- lib/Zend/CodeGenerator/Php/Method.php | 27 +- lib/Zend/CodeGenerator/Php/Parameter.php | 2 +- lib/Zend/CodeGenerator/Php/Property.php | 77 +- .../Php/Property/DefaultValue.php | 314 + lib/Zend/Config.php | 6 +- lib/Zend/Config/Exception.php | 5 +- lib/Zend/Config/Ini.php | 6 +- lib/Zend/Config/Writer.php | 6 +- lib/Zend/Config/Writer/Array.php | 6 +- lib/Zend/Config/Writer/Ini.php | 6 +- lib/Zend/Config/Writer/Xml.php | 15 +- lib/Zend/Config/Xml.php | 84 +- lib/Zend/Console/Getopt.php | 10 +- lib/Zend/Console/Getopt/Exception.php | 5 +- lib/Zend/Controller/Action.php | 5 +- lib/Zend/Controller/Action/Exception.php | 5 +- .../Controller/Action/Helper/Abstract.php | 5 +- .../Controller/Action/Helper/ActionStack.php | 6 +- .../Controller/Action/Helper/AjaxContext.php | 6 +- .../Action/Helper/AutoComplete/Abstract.php | 6 +- .../Action/Helper/AutoCompleteDojo.php | 6 +- .../Helper/AutoCompleteScriptaculous.php | 6 +- .../Action/Helper/ContextSwitch.php | 6 +- .../Action/Helper/FlashMessenger.php | 6 +- lib/Zend/Controller/Action/Helper/Json.php | 6 +- .../Controller/Action/Helper/Redirector.php | 5 +- lib/Zend/Controller/Action/Helper/Url.php | 6 +- .../Controller/Action/Helper/ViewRenderer.php | 33 +- lib/Zend/Controller/Action/HelperBroker.php | 5 +- .../Action/HelperBroker/PriorityStack.php | 5 +- lib/Zend/Controller/Action/Interface.php | 7 +- lib/Zend/Controller/Dispatcher/Abstract.php | 5 +- lib/Zend/Controller/Dispatcher/Exception.php | 5 +- lib/Zend/Controller/Dispatcher/Interface.php | 6 +- lib/Zend/Controller/Dispatcher/Standard.php | 5 +- lib/Zend/Controller/Exception.php | 5 +- lib/Zend/Controller/Front.php | 5 +- lib/Zend/Controller/Plugin/Abstract.php | 5 +- lib/Zend/Controller/Plugin/ActionStack.php | 6 +- lib/Zend/Controller/Plugin/Broker.php | 5 +- lib/Zend/Controller/Plugin/ErrorHandler.php | 6 +- lib/Zend/Controller/Plugin/PutHandler.php | 60 + lib/Zend/Controller/Request/Abstract.php | 5 +- lib/Zend/Controller/Request/Apache404.php | 3 +- lib/Zend/Controller/Request/Exception.php | 5 +- lib/Zend/Controller/Request/Http.php | 28 +- lib/Zend/Controller/Request/HttpTestCase.php | 3 +- lib/Zend/Controller/Request/Simple.php | 5 +- lib/Zend/Controller/Response/Abstract.php | 10 +- lib/Zend/Controller/Response/Cli.php | 5 +- lib/Zend/Controller/Response/Exception.php | 6 +- lib/Zend/Controller/Response/Http.php | 3 +- lib/Zend/Controller/Response/HttpTestCase.php | 3 +- lib/Zend/Controller/Router/Abstract.php | 5 +- lib/Zend/Controller/Router/Exception.php | 7 +- lib/Zend/Controller/Router/Interface.php | 6 +- lib/Zend/Controller/Router/Rewrite.php | 37 +- lib/Zend/Controller/Router/Route.php | 7 +- lib/Zend/Controller/Router/Route/Abstract.php | 7 +- lib/Zend/Controller/Router/Route/Chain.php | 7 +- lib/Zend/Controller/Router/Route/Hostname.php | 7 +- .../Controller/Router/Route/Interface.php | 7 +- lib/Zend/Controller/Router/Route/Module.php | 7 +- lib/Zend/Controller/Router/Route/Regex.php | 7 +- lib/Zend/Controller/Router/Route/Static.php | 7 +- lib/Zend/Crypt.php | 62 + lib/Zend/Crypt/DiffieHellman.php | 2 +- lib/Zend/Crypt/DiffieHellman/Exception.php | 2 +- lib/Zend/Crypt/Exception.php | 2 +- lib/Zend/Crypt/Hmac.php | 2 +- lib/Zend/Crypt/Hmac/Exception.php | 2 +- lib/Zend/Crypt/Math.php | 2 +- lib/Zend/Crypt/Math/BigInteger.php | 2 +- lib/Zend/Crypt/Math/BigInteger/Bcmath.php | 2 +- lib/Zend/Crypt/Math/BigInteger/Exception.php | 2 +- lib/Zend/Crypt/Math/BigInteger/Gmp.php | 2 +- lib/Zend/Crypt/Math/BigInteger/Interface.php | 2 +- lib/Zend/Crypt/Math/Exception.php | 2 +- lib/Zend/Crypt/Rsa.php | 2 +- lib/Zend/Crypt/Rsa/Key.php | 2 +- lib/Zend/Crypt/Rsa/Key/Private.php | 2 +- lib/Zend/Crypt/Rsa/Key/Public.php | 2 +- lib/Zend/Currency.php | 51 +- lib/Zend/Currency/Exception.php | 6 +- lib/Zend/Date.php | 283 +- lib/Zend/Date/Cities.php | 6 +- lib/Zend/Date/DateObject.php | 10 +- lib/Zend/Date/Exception.php | 6 +- lib/Zend/Db.php | 6 +- lib/Zend/Db/Adapter/Abstract.php | 26 +- lib/Zend/Db/Adapter/Db2.php | 6 +- lib/Zend/Db/Adapter/Db2/Exception.php | 6 +- lib/Zend/Db/Adapter/Exception.php | 5 +- lib/Zend/Db/Adapter/Mysqli.php | 6 +- lib/Zend/Db/Adapter/Mysqli/Exception.php | 5 +- lib/Zend/Db/Adapter/Oracle.php | 12 +- lib/Zend/Db/Adapter/Oracle/Exception.php | 5 +- lib/Zend/Db/Adapter/Pdo/Abstract.php | 14 +- lib/Zend/Db/Adapter/Pdo/Ibm.php | 6 +- lib/Zend/Db/Adapter/Pdo/Ibm/Db2.php | 6 +- lib/Zend/Db/Adapter/Pdo/Ibm/Ids.php | 6 +- lib/Zend/Db/Adapter/Pdo/Mssql.php | 84 +- lib/Zend/Db/Adapter/Pdo/Mysql.php | 8 +- lib/Zend/Db/Adapter/Pdo/Oci.php | 6 +- lib/Zend/Db/Adapter/Pdo/Pgsql.php | 8 +- lib/Zend/Db/Adapter/Pdo/Sqlite.php | 6 +- lib/Zend/Db/Adapter/Sqlsrv.php | 661 + lib/Zend/Db/Adapter/Sqlsrv/Exception.php | 63 + lib/Zend/Db/Exception.php | 5 +- lib/Zend/Db/Expr.php | 6 +- lib/Zend/Db/Profiler.php | 6 +- lib/Zend/Db/Profiler/Exception.php | 6 +- lib/Zend/Db/Profiler/Firebug.php | 5 +- lib/Zend/Db/Profiler/Query.php | 6 +- lib/Zend/Db/Select.php | 10 +- lib/Zend/Db/Select/Exception.php | 5 +- lib/Zend/Db/Statement.php | 5 +- lib/Zend/Db/Statement/Db2.php | 6 +- lib/Zend/Db/Statement/Db2/Exception.php | 6 +- lib/Zend/Db/Statement/Exception.php | 5 +- lib/Zend/Db/Statement/Interface.php | 5 +- lib/Zend/Db/Statement/Mysqli.php | 24 +- lib/Zend/Db/Statement/Mysqli/Exception.php | 6 +- lib/Zend/Db/Statement/Oracle.php | 5 +- lib/Zend/Db/Statement/Oracle/Exception.php | 5 +- lib/Zend/Db/Statement/Pdo.php | 6 +- lib/Zend/Db/Statement/Pdo/Ibm.php | 6 +- lib/Zend/Db/Statement/Pdo/Oci.php | 6 +- lib/Zend/Db/Statement/Sqlsrv.php | 414 + lib/Zend/Db/Statement/Sqlsrv/Exception.php | 59 + lib/Zend/Db/Table.php | 53 +- lib/Zend/Db/Table/Abstract.php | 114 +- lib/Zend/Db/Table/Definition.php | 131 + lib/Zend/Db/Table/Exception.php | 5 +- lib/Zend/Db/Table/Row.php | 6 +- lib/Zend/Db/Table/Row/Abstract.php | 180 +- lib/Zend/Db/Table/Row/Exception.php | 5 +- lib/Zend/Db/Table/Rowset.php | 6 +- lib/Zend/Db/Table/Rowset/Abstract.php | 6 +- lib/Zend/Db/Table/Rowset/Exception.php | 5 +- lib/Zend/Db/Table/Select.php | 8 +- lib/Zend/Db/Table/Select/Exception.php | 5 +- lib/Zend/Debug.php | 5 +- lib/Zend/Dojo.php | 6 +- lib/Zend/Dojo/BuildLayer.php | 536 + lib/Zend/Dojo/Data.php | 6 +- lib/Zend/Dojo/Exception.php | 6 +- lib/Zend/Dojo/Form.php | 6 +- .../Form/Decorator/AccordionContainer.php | 6 +- .../Dojo/Form/Decorator/AccordionPane.php | 6 +- .../Dojo/Form/Decorator/BorderContainer.php | 6 +- lib/Zend/Dojo/Form/Decorator/ContentPane.php | 6 +- .../Dojo/Form/Decorator/DijitContainer.php | 6 +- lib/Zend/Dojo/Form/Decorator/DijitElement.php | 6 +- lib/Zend/Dojo/Form/Decorator/DijitForm.php | 6 +- .../Dojo/Form/Decorator/SplitContainer.php | 6 +- .../Dojo/Form/Decorator/StackContainer.php | 6 +- lib/Zend/Dojo/Form/Decorator/TabContainer.php | 6 +- lib/Zend/Dojo/Form/DisplayGroup.php | 6 +- lib/Zend/Dojo/Form/Element/Button.php | 6 +- lib/Zend/Dojo/Form/Element/CheckBox.php | 35 +- lib/Zend/Dojo/Form/Element/ComboBox.php | 6 +- .../Dojo/Form/Element/CurrencyTextBox.php | 6 +- lib/Zend/Dojo/Form/Element/DateTextBox.php | 6 +- lib/Zend/Dojo/Form/Element/Dijit.php | 6 +- lib/Zend/Dojo/Form/Element/DijitMulti.php | 6 +- lib/Zend/Dojo/Form/Element/Editor.php | 5 +- .../Dojo/Form/Element/FilteringSelect.php | 6 +- .../Dojo/Form/Element/HorizontalSlider.php | 6 +- lib/Zend/Dojo/Form/Element/NumberSpinner.php | 6 +- lib/Zend/Dojo/Form/Element/NumberTextBox.php | 6 +- .../Dojo/Form/Element/PasswordTextBox.php | 6 +- lib/Zend/Dojo/Form/Element/RadioButton.php | 6 +- lib/Zend/Dojo/Form/Element/SimpleTextarea.php | 6 +- lib/Zend/Dojo/Form/Element/Slider.php | 6 +- lib/Zend/Dojo/Form/Element/SubmitButton.php | 6 +- lib/Zend/Dojo/Form/Element/TextBox.php | 6 +- lib/Zend/Dojo/Form/Element/Textarea.php | 6 +- lib/Zend/Dojo/Form/Element/TimeTextBox.php | 6 +- .../Dojo/Form/Element/ValidationTextBox.php | 6 +- lib/Zend/Dojo/Form/Element/VerticalSlider.php | 6 +- lib/Zend/Dojo/Form/SubForm.php | 6 +- lib/Zend/Dojo/View/Exception.php | 9 +- .../Dojo/View/Helper/AccordionContainer.php | 6 +- lib/Zend/Dojo/View/Helper/AccordionPane.php | 6 +- lib/Zend/Dojo/View/Helper/BorderContainer.php | 6 +- lib/Zend/Dojo/View/Helper/Button.php | 6 +- lib/Zend/Dojo/View/Helper/CheckBox.php | 10 +- lib/Zend/Dojo/View/Helper/ComboBox.php | 6 +- lib/Zend/Dojo/View/Helper/ContentPane.php | 6 +- lib/Zend/Dojo/View/Helper/CurrencyTextBox.php | 6 +- lib/Zend/Dojo/View/Helper/CustomDijit.php | 6 +- lib/Zend/Dojo/View/Helper/DateTextBox.php | 6 +- lib/Zend/Dojo/View/Helper/Dijit.php | 6 +- lib/Zend/Dojo/View/Helper/DijitContainer.php | 6 +- lib/Zend/Dojo/View/Helper/Dojo.php | 8 +- lib/Zend/Dojo/View/Helper/Dojo/Container.php | 8 +- lib/Zend/Dojo/View/Helper/Editor.php | 6 +- lib/Zend/Dojo/View/Helper/FilteringSelect.php | 6 +- lib/Zend/Dojo/View/Helper/Form.php | 6 +- .../Dojo/View/Helper/HorizontalSlider.php | 6 +- lib/Zend/Dojo/View/Helper/NumberSpinner.php | 6 +- lib/Zend/Dojo/View/Helper/NumberTextBox.php | 6 +- lib/Zend/Dojo/View/Helper/PasswordTextBox.php | 6 +- lib/Zend/Dojo/View/Helper/RadioButton.php | 6 +- lib/Zend/Dojo/View/Helper/SimpleTextarea.php | 10 +- lib/Zend/Dojo/View/Helper/Slider.php | 6 +- lib/Zend/Dojo/View/Helper/SplitContainer.php | 6 +- lib/Zend/Dojo/View/Helper/StackContainer.php | 6 +- lib/Zend/Dojo/View/Helper/SubmitButton.php | 6 +- lib/Zend/Dojo/View/Helper/TabContainer.php | 6 +- lib/Zend/Dojo/View/Helper/TextBox.php | 6 +- lib/Zend/Dojo/View/Helper/Textarea.php | 6 +- lib/Zend/Dojo/View/Helper/TimeTextBox.php | 6 +- .../Dojo/View/Helper/ValidationTextBox.php | 6 +- lib/Zend/Dojo/View/Helper/VerticalSlider.php | 6 +- lib/Zend/Dom/Exception.php | 7 +- lib/Zend/Dom/Query.php | 52 +- lib/Zend/Dom/Query/Css2Xpath.php | 28 +- lib/Zend/Dom/Query/Result.php | 8 +- lib/Zend/Exception.php | 5 +- lib/Zend/Feed.php | 6 +- lib/Zend/Feed/Abstract.php | 6 +- lib/Zend/Feed/Atom.php | 6 +- lib/Zend/Feed/Builder.php | 6 +- lib/Zend/Feed/Builder/Entry.php | 6 +- lib/Zend/Feed/Builder/Exception.php | 6 +- lib/Zend/Feed/Builder/Header.php | 6 +- lib/Zend/Feed/Builder/Header/Itunes.php | 6 +- lib/Zend/Feed/Builder/Interface.php | 6 +- lib/Zend/Feed/Element.php | 6 +- lib/Zend/Feed/Entry/Abstract.php | 6 +- lib/Zend/Feed/Entry/Atom.php | 6 +- lib/Zend/Feed/Entry/Rss.php | 6 +- lib/Zend/Feed/Exception.php | 6 +- lib/Zend/Feed/Reader.php | 692 + lib/Zend/Feed/Reader/Entry/Atom.php | 357 + lib/Zend/Feed/Reader/Entry/Rss.php | 611 + lib/Zend/Feed/Reader/EntryAbstract.php | 242 + lib/Zend/Feed/Reader/EntryInterface.php | 136 + lib/Zend/Feed/Reader/Extension/Atom/Entry.php | 506 + lib/Zend/Feed/Reader/Extension/Atom/Feed.php | 441 + .../Feed/Reader/Extension/Content/Entry.php | 64 + .../Extension/CreativeCommons/Entry.php | 97 + .../Reader/Extension/CreativeCommons/Feed.php | 89 + .../Reader/Extension/DublinCore/Entry.php | 232 + .../Feed/Reader/Extension/DublinCore/Feed.php | 265 + .../Feed/Reader/Extension/EntryAbstract.php | 197 + .../Feed/Reader/Extension/FeedAbstract.php | 166 + .../Feed/Reader/Extension/Podcast/Entry.php | 202 + .../Feed/Reader/Extension/Podcast/Feed.php | 293 + .../Feed/Reader/Extension/Slash/Entry.php | 144 + .../Reader/Extension/Syndication/Feed.php | 168 + .../Feed/Reader/Extension/Thread/Entry.php | 91 + .../Reader/Extension/WellFormedWeb/Entry.php | 73 + lib/Zend/Feed/Reader/Feed/Atom.php | 329 + lib/Zend/Feed/Reader/Feed/Rss.php | 533 + lib/Zend/Feed/Reader/FeedAbstract.php | 304 + lib/Zend/Feed/Reader/FeedInterface.php | 115 + lib/Zend/Feed/Rss.php | 6 +- lib/Zend/File/Transfer.php | 6 +- lib/Zend/File/Transfer/Adapter/Abstract.php | 70 +- lib/Zend/File/Transfer/Adapter/Http.php | 19 +- lib/Zend/File/Transfer/Exception.php | 6 +- lib/Zend/Filter.php | 89 +- lib/Zend/Filter/Alnum.php | 8 +- lib/Zend/Filter/Alpha.php | 8 +- lib/Zend/Filter/BaseName.php | 6 +- lib/Zend/Filter/Decrypt.php | 6 +- lib/Zend/Filter/Digits.php | 6 +- lib/Zend/Filter/Dir.php | 6 +- lib/Zend/Filter/Encrypt.php | 6 +- lib/Zend/Filter/Encrypt/Interface.php | 6 +- lib/Zend/Filter/Encrypt/Mcrypt.php | 6 +- lib/Zend/Filter/Encrypt/Openssl.php | 16 +- lib/Zend/Filter/Exception.php | 6 +- lib/Zend/Filter/File/Decrypt.php | 6 +- lib/Zend/Filter/File/Encrypt.php | 6 +- lib/Zend/Filter/File/LowerCase.php | 6 +- lib/Zend/Filter/File/Rename.php | 6 +- lib/Zend/Filter/File/UpperCase.php | 6 +- lib/Zend/Filter/HtmlEntities.php | 76 +- lib/Zend/Filter/Inflector.php | 111 +- lib/Zend/Filter/Input.php | 107 +- lib/Zend/Filter/Int.php | 6 +- lib/Zend/Filter/Interface.php | 6 +- lib/Zend/Filter/LocalizedToNormalized.php | 21 +- lib/Zend/Filter/NormalizedToLocalized.php | 8 +- lib/Zend/Filter/PregReplace.php | 42 +- lib/Zend/Filter/RealPath.php | 98 +- lib/Zend/Filter/StringToLower.php | 6 +- lib/Zend/Filter/StringToUpper.php | 6 +- lib/Zend/Filter/StringTrim.php | 33 +- lib/Zend/Filter/StripNewlines.php | 6 +- lib/Zend/Filter/StripTags.php | 8 +- lib/Zend/Filter/Word/CamelCaseToDash.php | 6 +- lib/Zend/Filter/Word/CamelCaseToSeparator.php | 6 +- .../Filter/Word/CamelCaseToUnderscore.php | 6 +- lib/Zend/Filter/Word/DashToCamelCase.php | 6 +- lib/Zend/Filter/Word/DashToSeparator.php | 6 +- lib/Zend/Filter/Word/DashToUnderscore.php | 6 +- lib/Zend/Filter/Word/Separator/Abstract.php | 6 +- lib/Zend/Filter/Word/SeparatorToCamelCase.php | 8 +- lib/Zend/Filter/Word/SeparatorToDash.php | 6 +- lib/Zend/Filter/Word/SeparatorToSeparator.php | 6 +- .../Filter/Word/UnderscoreToCamelCase.php | 6 +- lib/Zend/Filter/Word/UnderscoreToDash.php | 6 +- .../Filter/Word/UnderscoreToSeparator.php | 6 +- lib/Zend/Form.php | 578 +- lib/Zend/Form/Decorator/Abstract.php | 6 +- lib/Zend/Form/Decorator/Callback.php | 6 +- lib/Zend/Form/Decorator/Captcha.php | 6 +- lib/Zend/Form/Decorator/Captcha/Word.php | 6 +- lib/Zend/Form/Decorator/Description.php | 6 +- lib/Zend/Form/Decorator/DtDdWrapper.php | 6 +- lib/Zend/Form/Decorator/Errors.php | 6 +- lib/Zend/Form/Decorator/Exception.php | 6 +- lib/Zend/Form/Decorator/Fieldset.php | 6 +- lib/Zend/Form/Decorator/File.php | 6 +- lib/Zend/Form/Decorator/Form.php | 6 +- lib/Zend/Form/Decorator/FormElements.php | 6 +- lib/Zend/Form/Decorator/FormErrors.php | 6 +- lib/Zend/Form/Decorator/HtmlTag.php | 6 +- lib/Zend/Form/Decorator/Image.php | 6 +- lib/Zend/Form/Decorator/Interface.php | 6 +- lib/Zend/Form/Decorator/Label.php | 6 +- .../Form/Decorator/Marker/File/Interface.php | 6 +- lib/Zend/Form/Decorator/PrepareElements.php | 6 +- lib/Zend/Form/Decorator/Tooltip.php | 2 +- lib/Zend/Form/Decorator/ViewHelper.php | 6 +- lib/Zend/Form/Decorator/ViewScript.php | 6 +- lib/Zend/Form/DisplayGroup.php | 6 +- lib/Zend/Form/Element.php | 10 +- lib/Zend/Form/Element/Button.php | 6 +- lib/Zend/Form/Element/Captcha.php | 76 +- lib/Zend/Form/Element/Checkbox.php | 34 +- lib/Zend/Form/Element/Exception.php | 6 +- lib/Zend/Form/Element/File.php | 6 +- lib/Zend/Form/Element/Hash.php | 6 +- lib/Zend/Form/Element/Hidden.php | 6 +- lib/Zend/Form/Element/Image.php | 6 +- lib/Zend/Form/Element/Multi.php | 6 +- lib/Zend/Form/Element/MultiCheckbox.php | 6 +- lib/Zend/Form/Element/Multiselect.php | 6 +- lib/Zend/Form/Element/Password.php | 6 +- lib/Zend/Form/Element/Radio.php | 6 +- lib/Zend/Form/Element/Reset.php | 6 +- lib/Zend/Form/Element/Select.php | 6 +- lib/Zend/Form/Element/Submit.php | 6 +- lib/Zend/Form/Element/Text.php | 6 +- lib/Zend/Form/Element/Textarea.php | 6 +- lib/Zend/Form/Element/Xhtml.php | 6 +- lib/Zend/Form/Exception.php | 6 +- lib/Zend/Form/SubForm.php | 6 +- lib/Zend/Gdata.php | 5 +- lib/Zend/Gdata/App.php | 9 +- lib/Zend/Gdata/App/AuthException.php | 5 +- lib/Zend/Gdata/App/BadMethodCallException.php | 5 +- lib/Zend/Gdata/App/Base.php | 5 +- lib/Zend/Gdata/App/BaseMediaSource.php | 5 +- .../Gdata/App/CaptchaRequiredException.php | 5 +- lib/Zend/Gdata/App/Entry.php | 5 +- lib/Zend/Gdata/App/Exception.php | 5 +- lib/Zend/Gdata/App/Extension.php | 5 +- lib/Zend/Gdata/App/Extension/Author.php | 5 +- lib/Zend/Gdata/App/Extension/Category.php | 5 +- lib/Zend/Gdata/App/Extension/Content.php | 5 +- lib/Zend/Gdata/App/Extension/Contributor.php | 5 +- lib/Zend/Gdata/App/Extension/Control.php | 5 +- lib/Zend/Gdata/App/Extension/Draft.php | 5 +- lib/Zend/Gdata/App/Extension/Edited.php | 1 + lib/Zend/Gdata/App/Extension/Element.php | 5 +- lib/Zend/Gdata/App/Extension/Email.php | 5 +- lib/Zend/Gdata/App/Extension/Generator.php | 5 +- lib/Zend/Gdata/App/Extension/Icon.php | 5 +- lib/Zend/Gdata/App/Extension/Id.php | 5 +- lib/Zend/Gdata/App/Extension/Link.php | 5 +- lib/Zend/Gdata/App/Extension/Logo.php | 5 +- lib/Zend/Gdata/App/Extension/Name.php | 5 +- lib/Zend/Gdata/App/Extension/Person.php | 5 +- lib/Zend/Gdata/App/Extension/Published.php | 5 +- lib/Zend/Gdata/App/Extension/Rights.php | 5 +- lib/Zend/Gdata/App/Extension/Source.php | 5 +- lib/Zend/Gdata/App/Extension/Subtitle.php | 5 +- lib/Zend/Gdata/App/Extension/Summary.php | 5 +- lib/Zend/Gdata/App/Extension/Text.php | 5 +- lib/Zend/Gdata/App/Extension/Title.php | 5 +- lib/Zend/Gdata/App/Extension/Updated.php | 5 +- lib/Zend/Gdata/App/Extension/Uri.php | 5 +- lib/Zend/Gdata/App/Feed.php | 5 +- lib/Zend/Gdata/App/FeedEntryParent.php | 5 +- lib/Zend/Gdata/App/FeedSourceParent.php | 5 +- lib/Zend/Gdata/App/HttpException.php | 5 +- lib/Zend/Gdata/App/IOException.php | 5 +- .../Gdata/App/InvalidArgumentException.php | 5 +- .../App/LoggingHttpClientAdapterSocket.php | 9 +- lib/Zend/Gdata/App/MediaEntry.php | 1 + lib/Zend/Gdata/App/MediaFileSource.php | 5 +- lib/Zend/Gdata/App/MediaSource.php | 5 +- lib/Zend/Gdata/App/Util.php | 5 +- lib/Zend/Gdata/App/VersionException.php | 5 +- lib/Zend/Gdata/AuthSub.php | 5 +- lib/Zend/Gdata/Books.php | 8 +- lib/Zend/Gdata/Books/CollectionEntry.php | 7 +- lib/Zend/Gdata/Books/CollectionFeed.php | 7 +- .../Gdata/Books/Extension/AnnotationLink.php | 7 +- .../Gdata/Books/Extension/BooksCategory.php | 7 +- lib/Zend/Gdata/Books/Extension/BooksLink.php | 7 +- .../Gdata/Books/Extension/Embeddability.php | 7 +- lib/Zend/Gdata/Books/Extension/InfoLink.php | 6 +- .../Gdata/Books/Extension/PreviewLink.php | 7 +- lib/Zend/Gdata/Books/Extension/Review.php | 7 +- .../Gdata/Books/Extension/ThumbnailLink.php | 7 +- .../Gdata/Books/Extension/Viewability.php | 7 +- lib/Zend/Gdata/Books/VolumeEntry.php | 7 +- lib/Zend/Gdata/Books/VolumeFeed.php | 7 +- lib/Zend/Gdata/Books/VolumeQuery.php | 5 +- lib/Zend/Gdata/Calendar.php | 5 +- lib/Zend/Gdata/Calendar/EventEntry.php | 5 +- lib/Zend/Gdata/Calendar/EventFeed.php | 5 +- lib/Zend/Gdata/Calendar/EventQuery.php | 5 +- .../Gdata/Calendar/Extension/AccessLevel.php | 10 +- lib/Zend/Gdata/Calendar/Extension/Color.php | 5 +- lib/Zend/Gdata/Calendar/Extension/Hidden.php | 5 +- lib/Zend/Gdata/Calendar/Extension/Link.php | 6 +- .../Gdata/Calendar/Extension/QuickAdd.php | 5 +- .../Gdata/Calendar/Extension/Selected.php | 5 +- .../Extension/SendEventNotifications.php | 5 +- .../Gdata/Calendar/Extension/Timezone.php | 5 +- .../Gdata/Calendar/Extension/WebContent.php | 5 +- lib/Zend/Gdata/Calendar/ListEntry.php | 5 +- lib/Zend/Gdata/Calendar/ListFeed.php | 5 +- lib/Zend/Gdata/ClientLogin.php | 1 + lib/Zend/Gdata/Docs.php | 8 +- lib/Zend/Gdata/Docs/DocumentListEntry.php | 5 +- lib/Zend/Gdata/Docs/DocumentListFeed.php | 5 +- lib/Zend/Gdata/Docs/Query.php | 5 +- lib/Zend/Gdata/DublinCore.php | 5 +- .../Gdata/DublinCore/Extension/Creator.php | 7 +- lib/Zend/Gdata/DublinCore/Extension/Date.php | 7 +- .../DublinCore/Extension/Description.php | 7 +- .../Gdata/DublinCore/Extension/Format.php | 7 +- .../Gdata/DublinCore/Extension/Identifier.php | 7 +- .../Gdata/DublinCore/Extension/Language.php | 7 +- .../Gdata/DublinCore/Extension/Publisher.php | 7 +- .../Gdata/DublinCore/Extension/Rights.php | 7 +- .../Gdata/DublinCore/Extension/Subject.php | 7 +- lib/Zend/Gdata/DublinCore/Extension/Title.php | 7 +- lib/Zend/Gdata/Entry.php | 5 +- lib/Zend/Gdata/Exif.php | 5 +- lib/Zend/Gdata/Exif/Entry.php | 5 +- lib/Zend/Gdata/Exif/Extension/Distance.php | 5 +- lib/Zend/Gdata/Exif/Extension/Exposure.php | 5 +- lib/Zend/Gdata/Exif/Extension/FStop.php | 5 +- lib/Zend/Gdata/Exif/Extension/Flash.php | 5 +- lib/Zend/Gdata/Exif/Extension/FocalLength.php | 5 +- .../Gdata/Exif/Extension/ImageUniqueId.php | 5 +- lib/Zend/Gdata/Exif/Extension/Iso.php | 5 +- lib/Zend/Gdata/Exif/Extension/Make.php | 5 +- lib/Zend/Gdata/Exif/Extension/Model.php | 5 +- lib/Zend/Gdata/Exif/Extension/Tags.php | 5 +- lib/Zend/Gdata/Exif/Extension/Time.php | 5 +- lib/Zend/Gdata/Exif/Feed.php | 5 +- lib/Zend/Gdata/Extension.php | 5 +- lib/Zend/Gdata/Extension/AttendeeStatus.php | 5 +- lib/Zend/Gdata/Extension/AttendeeType.php | 5 +- lib/Zend/Gdata/Extension/Comments.php | 5 +- lib/Zend/Gdata/Extension/EntryLink.php | 5 +- lib/Zend/Gdata/Extension/EventStatus.php | 5 +- lib/Zend/Gdata/Extension/ExtendedProperty.php | 5 +- lib/Zend/Gdata/Extension/FeedLink.php | 5 +- .../Extension/OpenSearchItemsPerPage.php | 5 +- .../Gdata/Extension/OpenSearchStartIndex.php | 5 +- .../Extension/OpenSearchTotalResults.php | 5 +- lib/Zend/Gdata/Extension/OriginalEvent.php | 5 +- lib/Zend/Gdata/Extension/Rating.php | 5 +- lib/Zend/Gdata/Extension/Recurrence.php | 5 +- .../Gdata/Extension/RecurrenceException.php | 5 +- lib/Zend/Gdata/Extension/Reminder.php | 5 +- lib/Zend/Gdata/Extension/Transparency.php | 5 +- lib/Zend/Gdata/Extension/Visibility.php | 5 +- lib/Zend/Gdata/Extension/When.php | 5 +- lib/Zend/Gdata/Extension/Where.php | 5 +- lib/Zend/Gdata/Extension/Who.php | 5 +- lib/Zend/Gdata/Feed.php | 5 +- lib/Zend/Gdata/Gapps.php | 9 +- lib/Zend/Gdata/Gapps/EmailListEntry.php | 5 +- lib/Zend/Gdata/Gapps/EmailListFeed.php | 5 +- lib/Zend/Gdata/Gapps/EmailListQuery.php | 5 +- .../Gdata/Gapps/EmailListRecipientEntry.php | 5 +- .../Gdata/Gapps/EmailListRecipientFeed.php | 5 +- .../Gdata/Gapps/EmailListRecipientQuery.php | 5 +- lib/Zend/Gdata/Gapps/Error.php | 5 +- lib/Zend/Gdata/Gapps/Extension/EmailList.php | 5 +- lib/Zend/Gdata/Gapps/Extension/Login.php | 5 +- lib/Zend/Gdata/Gapps/Extension/Name.php | 5 +- lib/Zend/Gdata/Gapps/Extension/Nickname.php | 5 +- lib/Zend/Gdata/Gapps/Extension/Quota.php | 5 +- lib/Zend/Gdata/Gapps/NicknameEntry.php | 5 +- lib/Zend/Gdata/Gapps/NicknameFeed.php | 5 +- lib/Zend/Gdata/Gapps/NicknameQuery.php | 5 +- lib/Zend/Gdata/Gapps/Query.php | 5 +- lib/Zend/Gdata/Gapps/ServiceException.php | 5 +- lib/Zend/Gdata/Gapps/UserEntry.php | 5 +- lib/Zend/Gdata/Gapps/UserFeed.php | 5 +- lib/Zend/Gdata/Gapps/UserQuery.php | 5 +- lib/Zend/Gdata/Gbase.php | 5 +- lib/Zend/Gdata/Gbase/Entry.php | 5 +- .../Gdata/Gbase/Extension/BaseAttribute.php | 6 +- lib/Zend/Gdata/Gbase/Feed.php | 5 +- lib/Zend/Gdata/Gbase/ItemEntry.php | 5 +- lib/Zend/Gdata/Gbase/ItemFeed.php | 5 +- lib/Zend/Gdata/Gbase/ItemQuery.php | 5 +- lib/Zend/Gdata/Gbase/Query.php | 5 +- lib/Zend/Gdata/Gbase/SnippetEntry.php | 5 +- lib/Zend/Gdata/Gbase/SnippetFeed.php | 5 +- lib/Zend/Gdata/Gbase/SnippetQuery.php | 5 +- lib/Zend/Gdata/Geo.php | 5 +- lib/Zend/Gdata/Geo/Entry.php | 5 +- lib/Zend/Gdata/Geo/Extension/GeoRssWhere.php | 5 +- lib/Zend/Gdata/Geo/Extension/GmlPoint.php | 5 +- lib/Zend/Gdata/Geo/Extension/GmlPos.php | 5 +- lib/Zend/Gdata/Geo/Feed.php | 5 +- lib/Zend/Gdata/Health.php | 5 +- lib/Zend/Gdata/Health/Extension/Ccr.php | 6 +- lib/Zend/Gdata/Health/ProfileEntry.php | 5 +- lib/Zend/Gdata/Health/ProfileFeed.php | 5 +- lib/Zend/Gdata/Health/ProfileListEntry.php | 5 +- lib/Zend/Gdata/Health/ProfileListFeed.php | 5 +- lib/Zend/Gdata/Health/Query.php | 5 +- lib/Zend/Gdata/HttpAdapterStreamingProxy.php | 1 + lib/Zend/Gdata/HttpAdapterStreamingSocket.php | 1 + lib/Zend/Gdata/HttpClient.php | 1 + lib/Zend/Gdata/Kind/EventEntry.php | 5 +- lib/Zend/Gdata/Media.php | 5 +- lib/Zend/Gdata/Media/Entry.php | 5 +- .../Gdata/Media/Extension/MediaCategory.php | 5 +- .../Gdata/Media/Extension/MediaContent.php | 5 +- .../Gdata/Media/Extension/MediaCopyright.php | 5 +- .../Gdata/Media/Extension/MediaCredit.php | 5 +- .../Media/Extension/MediaDescription.php | 5 +- lib/Zend/Gdata/Media/Extension/MediaGroup.php | 5 +- lib/Zend/Gdata/Media/Extension/MediaHash.php | 5 +- .../Gdata/Media/Extension/MediaKeywords.php | 5 +- .../Gdata/Media/Extension/MediaPlayer.php | 5 +- .../Gdata/Media/Extension/MediaRating.php | 5 +- .../Media/Extension/MediaRestriction.php | 5 +- lib/Zend/Gdata/Media/Extension/MediaText.php | 5 +- .../Gdata/Media/Extension/MediaThumbnail.php | 5 +- lib/Zend/Gdata/Media/Extension/MediaTitle.php | 5 +- lib/Zend/Gdata/Media/Feed.php | 5 +- lib/Zend/Gdata/MediaMimeStream.php | 1 + lib/Zend/Gdata/MimeBodyString.php | 1 + lib/Zend/Gdata/MimeFile.php | 1 + lib/Zend/Gdata/Photos.php | 5 +- lib/Zend/Gdata/Photos/AlbumEntry.php | 5 +- lib/Zend/Gdata/Photos/AlbumFeed.php | 5 +- lib/Zend/Gdata/Photos/AlbumQuery.php | 5 +- lib/Zend/Gdata/Photos/CommentEntry.php | 5 +- lib/Zend/Gdata/Photos/Extension/Access.php | 5 +- lib/Zend/Gdata/Photos/Extension/AlbumId.php | 5 +- lib/Zend/Gdata/Photos/Extension/BytesUsed.php | 5 +- lib/Zend/Gdata/Photos/Extension/Checksum.php | 5 +- lib/Zend/Gdata/Photos/Extension/Client.php | 5 +- .../Gdata/Photos/Extension/CommentCount.php | 5 +- .../Photos/Extension/CommentingEnabled.php | 5 +- lib/Zend/Gdata/Photos/Extension/Height.php | 5 +- lib/Zend/Gdata/Photos/Extension/Id.php | 5 +- lib/Zend/Gdata/Photos/Extension/Location.php | 5 +- .../Photos/Extension/MaxPhotosPerAlbum.php | 5 +- lib/Zend/Gdata/Photos/Extension/Name.php | 5 +- lib/Zend/Gdata/Photos/Extension/Nickname.php | 5 +- lib/Zend/Gdata/Photos/Extension/NumPhotos.php | 5 +- .../Photos/Extension/NumPhotosRemaining.php | 5 +- lib/Zend/Gdata/Photos/Extension/PhotoId.php | 5 +- lib/Zend/Gdata/Photos/Extension/Position.php | 5 +- .../Gdata/Photos/Extension/QuotaCurrent.php | 5 +- .../Gdata/Photos/Extension/QuotaLimit.php | 5 +- lib/Zend/Gdata/Photos/Extension/Rotation.php | 5 +- lib/Zend/Gdata/Photos/Extension/Size.php | 5 +- lib/Zend/Gdata/Photos/Extension/Thumbnail.php | 5 +- lib/Zend/Gdata/Photos/Extension/Timestamp.php | 5 +- lib/Zend/Gdata/Photos/Extension/User.php | 5 +- lib/Zend/Gdata/Photos/Extension/Version.php | 5 +- lib/Zend/Gdata/Photos/Extension/Weight.php | 5 +- lib/Zend/Gdata/Photos/Extension/Width.php | 5 +- lib/Zend/Gdata/Photos/PhotoEntry.php | 5 +- lib/Zend/Gdata/Photos/PhotoFeed.php | 5 +- lib/Zend/Gdata/Photos/PhotoQuery.php | 5 +- lib/Zend/Gdata/Photos/TagEntry.php | 5 +- lib/Zend/Gdata/Photos/UserEntry.php | 5 +- lib/Zend/Gdata/Photos/UserFeed.php | 5 +- lib/Zend/Gdata/Photos/UserQuery.php | 5 +- lib/Zend/Gdata/Query.php | 5 +- lib/Zend/Gdata/Spreadsheets.php | 5 +- lib/Zend/Gdata/Spreadsheets/CellEntry.php | 5 +- lib/Zend/Gdata/Spreadsheets/CellFeed.php | 5 +- lib/Zend/Gdata/Spreadsheets/CellQuery.php | 5 +- lib/Zend/Gdata/Spreadsheets/DocumentQuery.php | 5 +- .../Gdata/Spreadsheets/Extension/Cell.php | 6 +- .../Gdata/Spreadsheets/Extension/ColCount.php | 6 +- .../Gdata/Spreadsheets/Extension/Custom.php | 6 +- .../Gdata/Spreadsheets/Extension/RowCount.php | 6 +- lib/Zend/Gdata/Spreadsheets/ListEntry.php | 5 +- lib/Zend/Gdata/Spreadsheets/ListFeed.php | 5 +- lib/Zend/Gdata/Spreadsheets/ListQuery.php | 5 +- .../Gdata/Spreadsheets/SpreadsheetEntry.php | 5 +- .../Gdata/Spreadsheets/SpreadsheetFeed.php | 5 +- .../Gdata/Spreadsheets/WorksheetEntry.php | 5 +- lib/Zend/Gdata/Spreadsheets/WorksheetFeed.php | 5 +- lib/Zend/Gdata/YouTube.php | 1 + lib/Zend/Gdata/YouTube/ActivityEntry.php | 1 + lib/Zend/Gdata/YouTube/ActivityFeed.php | 1 + lib/Zend/Gdata/YouTube/CommentEntry.php | 5 +- lib/Zend/Gdata/YouTube/CommentFeed.php | 5 +- lib/Zend/Gdata/YouTube/ContactEntry.php | 5 +- lib/Zend/Gdata/YouTube/ContactFeed.php | 5 +- lib/Zend/Gdata/YouTube/Extension/AboutMe.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Age.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Books.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Company.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Control.php | 5 +- .../Gdata/YouTube/Extension/CountHint.php | 5 +- .../Gdata/YouTube/Extension/Description.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Duration.php | 5 +- .../Gdata/YouTube/Extension/FirstName.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Gender.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Hobbies.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Hometown.php | 5 +- lib/Zend/Gdata/YouTube/Extension/LastName.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Link.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Location.php | 5 +- .../Gdata/YouTube/Extension/MediaContent.php | 5 +- .../Gdata/YouTube/Extension/MediaCredit.php | 7 +- .../Gdata/YouTube/Extension/MediaGroup.php | 5 +- .../Gdata/YouTube/Extension/MediaRating.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Movies.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Music.php | 5 +- lib/Zend/Gdata/YouTube/Extension/NoEmbed.php | 5 +- .../Gdata/YouTube/Extension/Occupation.php | 5 +- .../Gdata/YouTube/Extension/PlaylistId.php | 5 +- .../Gdata/YouTube/Extension/PlaylistTitle.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Position.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Private.php | 5 +- .../Gdata/YouTube/Extension/QueryString.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Racy.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Recorded.php | 5 +- .../Gdata/YouTube/Extension/Relationship.php | 5 +- .../Gdata/YouTube/Extension/ReleaseDate.php | 5 +- lib/Zend/Gdata/YouTube/Extension/School.php | 5 +- lib/Zend/Gdata/YouTube/Extension/State.php | 5 +- .../Gdata/YouTube/Extension/Statistics.php | 1 + lib/Zend/Gdata/YouTube/Extension/Status.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Token.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Uploaded.php | 5 +- lib/Zend/Gdata/YouTube/Extension/Username.php | 5 +- lib/Zend/Gdata/YouTube/Extension/VideoId.php | 5 +- lib/Zend/Gdata/YouTube/InboxEntry.php | 1 + lib/Zend/Gdata/YouTube/InboxFeed.php | 5 +- lib/Zend/Gdata/YouTube/MediaEntry.php | 5 +- lib/Zend/Gdata/YouTube/PlaylistListEntry.php | 5 +- lib/Zend/Gdata/YouTube/PlaylistListFeed.php | 5 +- lib/Zend/Gdata/YouTube/PlaylistVideoEntry.php | 5 +- lib/Zend/Gdata/YouTube/PlaylistVideoFeed.php | 5 +- lib/Zend/Gdata/YouTube/SubscriptionEntry.php | 5 +- lib/Zend/Gdata/YouTube/SubscriptionFeed.php | 5 +- lib/Zend/Gdata/YouTube/UserProfileEntry.php | 5 +- lib/Zend/Gdata/YouTube/VideoEntry.php | 8 +- lib/Zend/Gdata/YouTube/VideoFeed.php | 5 +- lib/Zend/Gdata/YouTube/VideoQuery.php | 5 +- lib/Zend/Http/Client.php | 158 +- lib/Zend/Http/Client/Adapter/Curl.php | 55 +- lib/Zend/Http/Client/Adapter/Exception.php | 13 +- lib/Zend/Http/Client/Adapter/Interface.php | 6 +- lib/Zend/Http/Client/Adapter/Proxy.php | 100 +- lib/Zend/Http/Client/Adapter/Socket.php | 187 +- lib/Zend/Http/Client/Adapter/Test.php | 34 +- lib/Zend/Http/Client/Exception.php | 9 +- lib/Zend/Http/Cookie.php | 92 +- lib/Zend/Http/CookieJar.php | 56 +- lib/Zend/Http/Exception.php | 9 +- lib/Zend/Http/Response.php | 58 +- lib/Zend/InfoCard.php | 6 +- lib/Zend/InfoCard/Adapter/Default.php | 6 +- lib/Zend/InfoCard/Adapter/Exception.php | 8 +- lib/Zend/InfoCard/Adapter/Interface.php | 6 +- lib/Zend/InfoCard/Cipher.php | 6 +- lib/Zend/InfoCard/Cipher/Exception.php | 6 +- .../InfoCard/Cipher/Pki/Adapter/Abstract.php | 6 +- lib/Zend/InfoCard/Cipher/Pki/Adapter/Rsa.php | 6 +- lib/Zend/InfoCard/Cipher/Pki/Interface.php | 6 +- .../InfoCard/Cipher/Pki/Rsa/Interface.php | 6 +- .../Cipher/Symmetric/Adapter/Abstract.php | 6 +- .../Cipher/Symmetric/Adapter/Aes128cbc.php | 6 +- .../Cipher/Symmetric/Adapter/Aes256cbc.php | 6 +- .../Cipher/Symmetric/Aes128cbc/Interface.php | 6 +- .../Cipher/Symmetric/Aes256cbc/Interface.php | 6 +- .../InfoCard/Cipher/Symmetric/Interface.php | 6 +- lib/Zend/InfoCard/Claims.php | 6 +- lib/Zend/InfoCard/Exception.php | 6 +- lib/Zend/InfoCard/Xml/Assertion.php | 6 +- lib/Zend/InfoCard/Xml/Assertion/Interface.php | 6 +- lib/Zend/InfoCard/Xml/Assertion/Saml.php | 6 +- lib/Zend/InfoCard/Xml/Element.php | 6 +- lib/Zend/InfoCard/Xml/Element/Interface.php | 6 +- lib/Zend/InfoCard/Xml/EncryptedData.php | 6 +- .../InfoCard/Xml/EncryptedData/Abstract.php | 6 +- .../InfoCard/Xml/EncryptedData/XmlEnc.php | 6 +- lib/Zend/InfoCard/Xml/EncryptedKey.php | 6 +- lib/Zend/InfoCard/Xml/Exception.php | 6 +- lib/Zend/InfoCard/Xml/KeyInfo.php | 6 +- lib/Zend/InfoCard/Xml/KeyInfo/Abstract.php | 6 +- lib/Zend/InfoCard/Xml/KeyInfo/Default.php | 6 +- lib/Zend/InfoCard/Xml/KeyInfo/Interface.php | 6 +- lib/Zend/InfoCard/Xml/KeyInfo/XmlDSig.php | 6 +- lib/Zend/InfoCard/Xml/Security.php | 6 +- lib/Zend/InfoCard/Xml/Security/Exception.php | 6 +- lib/Zend/InfoCard/Xml/Security/Transform.php | 6 +- .../Security/Transform/EnvelopedSignature.php | 6 +- .../Xml/Security/Transform/Exception.php | 6 +- .../Xml/Security/Transform/Interface.php | 6 +- .../Xml/Security/Transform/XmlExcC14N.php | 6 +- .../InfoCard/Xml/SecurityTokenReference.php | 6 +- lib/Zend/Json.php | 5 +- lib/Zend/Json/Decoder.php | 5 +- lib/Zend/Json/Encoder.php | 5 +- lib/Zend/Json/Exception.php | 5 +- lib/Zend/Json/Expr.php | 6 +- lib/Zend/Json/Server.php | 5 +- lib/Zend/Json/Server/Cache.php | 6 +- lib/Zend/Json/Server/Error.php | 5 +- lib/Zend/Json/Server/Exception.php | 5 +- lib/Zend/Json/Server/Request.php | 5 +- lib/Zend/Json/Server/Request/Http.php | 5 +- lib/Zend/Json/Server/Response.php | 5 +- lib/Zend/Json/Server/Response/Http.php | 5 +- lib/Zend/Json/Server/Smd.php | 5 +- lib/Zend/Json/Server/Smd/Service.php | 6 +- lib/Zend/Layout.php | 14 +- .../Controller/Action/Helper/Layout.php | 6 +- lib/Zend/Layout/Controller/Plugin/Layout.php | 6 +- lib/Zend/Layout/Exception.php | 5 +- lib/Zend/Ldap.php | 1195 +- lib/Zend/Ldap/Attribute.php | 364 + lib/Zend/Ldap/Collection.php | 197 + lib/Zend/Ldap/Collection/Iterator/Default.php | 253 + .../Collection/Iterator/Interface.php} | 87 +- lib/Zend/Ldap/Converter.php | 71 + lib/Zend/Ldap/Dn.php | 794 + lib/Zend/Ldap/Exception.php | 256 +- lib/Zend/Ldap/Filter.php | 265 + lib/Zend/Ldap/Filter/Abstract.php | 157 + lib/Zend/Ldap/Filter/And.php | 48 + lib/Zend/Ldap/Filter/Exception.php | 37 + lib/Zend/Ldap/Filter/Logical.php | 107 + lib/Zend/Ldap/Filter/Mask.php | 66 + lib/Zend/Ldap/Filter/Not.php | 75 + lib/Zend/Ldap/Filter/Or.php | 48 + lib/Zend/Ldap/Filter/String.php | 65 + lib/Zend/Ldap/Ldif/Encoder.php | 301 + lib/Zend/Ldap/Node.php | 1098 ++ lib/Zend/Ldap/Node/Abstract.php | 485 + lib/Zend/Ldap/Node/ChildrenIterator.php | 209 + lib/Zend/Ldap/Node/Collection.php | 67 + lib/Zend/Ldap/Node/RootDse.php | 158 + .../Ldap/Node/RootDse/ActiveDirectory.php | 247 + lib/Zend/Ldap/Node/RootDse/OpenLdap.php | 102 + lib/Zend/Ldap/Node/RootDse/eDirectory.php | 160 + lib/Zend/Ldap/Node/Schema.php | 120 + lib/Zend/Ldap/Node/Schema/ActiveDirectory.php | 103 + .../Schema/AttributeType/ActiveDirectory.php | 104 + .../Node/Schema/AttributeType/Interface.php | 75 + .../Node/Schema/AttributeType/OpenLdap.php | 129 + lib/Zend/Ldap/Node/Schema/Item.php | 163 + .../Schema/ObjectClass/ActiveDirectory.php | 115 + .../Node/Schema/ObjectClass/Interface.php | 83 + .../Ldap/Node/Schema/ObjectClass/OpenLdap.php | 175 + lib/Zend/Ldap/Node/Schema/OpenLdap.php | 502 + lib/Zend/Loader.php | 6 +- lib/Zend/Loader/Autoloader.php | 8 +- lib/Zend/Loader/Autoloader/Interface.php | 8 +- lib/Zend/Loader/Autoloader/Resource.php | 13 +- lib/Zend/Loader/Exception.php | 6 +- lib/Zend/Loader/PluginLoader.php | 8 +- lib/Zend/Loader/PluginLoader/Exception.php | 5 +- lib/Zend/Loader/PluginLoader/Interface.php | 5 +- lib/Zend/Locale.php | 171 +- lib/Zend/Locale/Data.php | 169 +- lib/Zend/Locale/Data/Translation.php | 6 +- lib/Zend/Locale/Data/aa.xml | 55 +- lib/Zend/Locale/Data/aa_DJ.xml | 17 +- lib/Zend/Locale/Data/aa_ER.xml | 17 +- lib/Zend/Locale/Data/aa_ER_SAAHO.xml | 39 + lib/Zend/Locale/Data/aa_ET.xml | 6 +- lib/Zend/Locale/Data/af.xml | 41 +- lib/Zend/Locale/Data/af_NA.xml | 15 +- lib/Zend/Locale/Data/af_ZA.xml | 6 +- lib/Zend/Locale/Data/ak.xml | 21 +- lib/Zend/Locale/Data/ak_GH.xml | 6 +- lib/Zend/Locale/Data/am.xml | 41 +- lib/Zend/Locale/Data/am_ET.xml | 6 +- lib/Zend/Locale/Data/ar.xml | 6219 ++++---- lib/Zend/Locale/Data/ar_AE.xml | 6 +- lib/Zend/Locale/Data/ar_BH.xml | 6 +- lib/Zend/Locale/Data/ar_DZ.xml | 30 +- lib/Zend/Locale/Data/ar_EG.xml | 6 +- lib/Zend/Locale/Data/ar_IQ.xml | 6 +- lib/Zend/Locale/Data/ar_JO.xml | 6 +- lib/Zend/Locale/Data/ar_KW.xml | 6 +- lib/Zend/Locale/Data/ar_LB.xml | 11 +- lib/Zend/Locale/Data/ar_LY.xml | 6 +- lib/Zend/Locale/Data/ar_MA.xml | 33 +- lib/Zend/Locale/Data/ar_OM.xml | 6 +- lib/Zend/Locale/Data/ar_QA.xml | 6 +- lib/Zend/Locale/Data/ar_SA.xml | 6 +- lib/Zend/Locale/Data/ar_SD.xml | 6 +- lib/Zend/Locale/Data/ar_SY.xml | 6 +- lib/Zend/Locale/Data/ar_TN.xml | 27 +- lib/Zend/Locale/Data/ar_YE.xml | 6 +- lib/Zend/Locale/Data/as.xml | 20 +- lib/Zend/Locale/Data/as_IN.xml | 6 +- lib/Zend/Locale/Data/az.xml | 64 +- lib/Zend/Locale/Data/az_AZ.xml | 6 +- lib/Zend/Locale/Data/az_Cyrl.xml | 8 +- lib/Zend/Locale/Data/az_Cyrl_AZ.xml | 6 +- lib/Zend/Locale/Data/az_Latn.xml | 6 +- lib/Zend/Locale/Data/az_Latn_AZ.xml | 6 +- lib/Zend/Locale/Data/be.xml | 170 +- lib/Zend/Locale/Data/be_BY.xml | 6 +- lib/Zend/Locale/Data/bg.xml | 1401 +- lib/Zend/Locale/Data/bg_BG.xml | 6 +- lib/Zend/Locale/Data/bn.xml | 381 +- lib/Zend/Locale/Data/bn_BD.xml | 6 +- lib/Zend/Locale/Data/bn_IN.xml | 176 +- lib/Zend/Locale/Data/bo.xml | 441 + lib/Zend/Locale/Data/bo_CN.xml | 10 + lib/Zend/Locale/Data/bo_IN.xml | 10 + lib/Zend/Locale/Data/bs.xml | 21 +- lib/Zend/Locale/Data/bs_BA.xml | 6 +- lib/Zend/Locale/Data/byn.xml | 26 +- lib/Zend/Locale/Data/byn_ER.xml | 6 +- lib/Zend/Locale/Data/ca.xml | 2123 ++- lib/Zend/Locale/Data/ca_ES.xml | 6 +- lib/Zend/Locale/Data/cch.xml | 23 +- lib/Zend/Locale/Data/cch_NG.xml | 6 +- lib/Zend/Locale/Data/characters.xml | 2362 +-- lib/Zend/Locale/Data/cop.xml | 19 +- lib/Zend/Locale/Data/cs.xml | 494 +- lib/Zend/Locale/Data/cs_CZ.xml | 6 +- lib/Zend/Locale/Data/cy.xml | 43 +- lib/Zend/Locale/Data/cy_GB.xml | 6 +- lib/Zend/Locale/Data/da.xml | 458 +- lib/Zend/Locale/Data/da_DK.xml | 6 +- lib/Zend/Locale/Data/de.xml | 362 +- lib/Zend/Locale/Data/de_AT.xml | 23 +- lib/Zend/Locale/Data/de_BE.xml | 32 +- lib/Zend/Locale/Data/de_CH.xml | 12 +- lib/Zend/Locale/Data/de_DE.xml | 6 +- lib/Zend/Locale/Data/de_LI.xml | 7 +- lib/Zend/Locale/Data/de_LU.xml | 6 +- lib/Zend/Locale/Data/dv.xml | 21 +- lib/Zend/Locale/Data/dv_MV.xml | 6 +- lib/Zend/Locale/Data/dz.xml | 32 +- lib/Zend/Locale/Data/dz_BT.xml | 6 +- lib/Zend/Locale/Data/ee.xml | 22 +- lib/Zend/Locale/Data/ee_GH.xml | 6 +- lib/Zend/Locale/Data/ee_TG.xml | 6 +- lib/Zend/Locale/Data/el.xml | 2012 ++- lib/Zend/Locale/Data/el_CY.xml | 7 +- lib/Zend/Locale/Data/el_GR.xml | 6 +- lib/Zend/Locale/Data/el_POLYTON.xml | 12 +- lib/Zend/Locale/Data/en.xml | 8141 +++++----- lib/Zend/Locale/Data/en_AS.xml | 6 +- lib/Zend/Locale/Data/en_AU.xml | 29 +- lib/Zend/Locale/Data/en_BE.xml | 30 +- lib/Zend/Locale/Data/en_BW.xml | 32 +- lib/Zend/Locale/Data/en_BZ.xml | 32 +- lib/Zend/Locale/Data/en_CA.xml | 23 +- lib/Zend/Locale/Data/en_Dsrt.xml | 1024 ++ lib/Zend/Locale/Data/en_Dsrt_US.xml | 11 + lib/Zend/Locale/Data/en_GB.xml | 33 +- lib/Zend/Locale/Data/en_GU.xml | 6 +- lib/Zend/Locale/Data/en_HK.xml | 109 +- lib/Zend/Locale/Data/en_IE.xml | 34 +- lib/Zend/Locale/Data/en_IN.xml | 31 +- lib/Zend/Locale/Data/en_JM.xml | 14 +- lib/Zend/Locale/Data/en_MH.xml | 6 +- lib/Zend/Locale/Data/en_MP.xml | 6 +- lib/Zend/Locale/Data/en_MT.xml | 34 +- lib/Zend/Locale/Data/en_NA.xml | 14 +- lib/Zend/Locale/Data/en_NZ.xml | 30 +- lib/Zend/Locale/Data/en_PH.xml | 6 +- lib/Zend/Locale/Data/en_PK.xml | 39 +- lib/Zend/Locale/Data/en_SG.xml | 129 +- lib/Zend/Locale/Data/en_Shaw.xml | 14 +- lib/Zend/Locale/Data/en_TT.xml | 14 +- lib/Zend/Locale/Data/en_UM.xml | 6 +- lib/Zend/Locale/Data/en_US.xml | 6 +- lib/Zend/Locale/Data/en_US_POSIX.xml | 84 +- lib/Zend/Locale/Data/en_VI.xml | 6 +- lib/Zend/Locale/Data/en_ZA.xml | 31 +- lib/Zend/Locale/Data/en_ZW.xml | 36 +- lib/Zend/Locale/Data/eo.xml | 36 +- lib/Zend/Locale/Data/es.xml | 427 +- lib/Zend/Locale/Data/es_AR.xml | 29 +- lib/Zend/Locale/Data/es_BO.xml | 6 +- lib/Zend/Locale/Data/es_CL.xml | 28 +- lib/Zend/Locale/Data/es_CO.xml | 29 +- lib/Zend/Locale/Data/es_CR.xml | 13 +- lib/Zend/Locale/Data/es_DO.xml | 6 +- lib/Zend/Locale/Data/es_EC.xml | 28 +- lib/Zend/Locale/Data/es_ES.xml | 7 +- lib/Zend/Locale/Data/es_GT.xml | 27 +- lib/Zend/Locale/Data/es_HN.xml | 31 +- lib/Zend/Locale/Data/es_MX.xml | 6 +- lib/Zend/Locale/Data/es_NI.xml | 6 +- lib/Zend/Locale/Data/es_PA.xml | 26 +- lib/Zend/Locale/Data/es_PE.xml | 24 +- lib/Zend/Locale/Data/es_PR.xml | 26 +- lib/Zend/Locale/Data/es_PY.xml | 7 +- lib/Zend/Locale/Data/es_SV.xml | 6 +- lib/Zend/Locale/Data/es_US.xml | 34 +- lib/Zend/Locale/Data/es_UY.xml | 9 +- lib/Zend/Locale/Data/es_VE.xml | 7 +- lib/Zend/Locale/Data/et.xml | 99 +- lib/Zend/Locale/Data/et_EE.xml | 6 +- lib/Zend/Locale/Data/eu.xml | 515 +- lib/Zend/Locale/Data/eu_ES.xml | 6 +- lib/Zend/Locale/Data/fa.xml | 484 +- lib/Zend/Locale/Data/fa_AF.xml | 9 +- lib/Zend/Locale/Data/fa_IR.xml | 6 +- lib/Zend/Locale/Data/fi.xml | 1320 +- lib/Zend/Locale/Data/fi_FI.xml | 6 +- lib/Zend/Locale/Data/fil.xml | 63 +- lib/Zend/Locale/Data/fil_PH.xml | 16 +- lib/Zend/Locale/Data/fo.xml | 209 +- lib/Zend/Locale/Data/fo_FO.xml | 6 +- lib/Zend/Locale/Data/fr.xml | 915 +- lib/Zend/Locale/Data/fr_BE.xml | 25 +- lib/Zend/Locale/Data/fr_CA.xml | 25 +- lib/Zend/Locale/Data/fr_CH.xml | 27 +- lib/Zend/Locale/Data/fr_FR.xml | 6 +- lib/Zend/Locale/Data/fr_LU.xml | 6 +- lib/Zend/Locale/Data/fr_MC.xml | 6 +- lib/Zend/Locale/Data/fr_SN.xml | 6 +- lib/Zend/Locale/Data/fur.xml | 190 +- lib/Zend/Locale/Data/fur_IT.xml | 6 +- lib/Zend/Locale/Data/ga.xml | 128 +- lib/Zend/Locale/Data/ga_IE.xml | 6 +- lib/Zend/Locale/Data/gaa.xml | 20 +- lib/Zend/Locale/Data/gaa_GH.xml | 6 +- lib/Zend/Locale/Data/gez.xml | 30 +- lib/Zend/Locale/Data/gez_ER.xml | 6 +- lib/Zend/Locale/Data/gez_ET.xml | 16 +- lib/Zend/Locale/Data/gl.xml | 42 +- lib/Zend/Locale/Data/gl_ES.xml | 6 +- lib/Zend/Locale/Data/gsw.xml | 2920 ++++ lib/Zend/Locale/Data/gsw_CH.xml | 10 + lib/Zend/Locale/Data/gu.xml | 1030 +- lib/Zend/Locale/Data/gu_IN.xml | 6 +- lib/Zend/Locale/Data/gv.xml | 21 +- lib/Zend/Locale/Data/gv_GB.xml | 6 +- lib/Zend/Locale/Data/ha.xml | 29 +- lib/Zend/Locale/Data/ha_Arab.xml | 6 +- lib/Zend/Locale/Data/ha_Arab_NG.xml | 6 +- lib/Zend/Locale/Data/ha_Arab_SD.xml | 6 +- lib/Zend/Locale/Data/ha_GH.xml | 6 +- lib/Zend/Locale/Data/ha_Latn.xml | 6 +- lib/Zend/Locale/Data/ha_Latn_GH.xml | 6 +- lib/Zend/Locale/Data/ha_Latn_NE.xml | 6 +- lib/Zend/Locale/Data/ha_Latn_NG.xml | 6 +- lib/Zend/Locale/Data/ha_NE.xml | 6 +- lib/Zend/Locale/Data/ha_NG.xml | 6 +- lib/Zend/Locale/Data/ha_SD.xml | 6 +- lib/Zend/Locale/Data/haw.xml | 21 +- lib/Zend/Locale/Data/haw_US.xml | 6 +- lib/Zend/Locale/Data/he.xml | 514 +- lib/Zend/Locale/Data/he_IL.xml | 6 +- lib/Zend/Locale/Data/hi.xml | 655 +- lib/Zend/Locale/Data/hi_IN.xml | 6 +- lib/Zend/Locale/Data/hr.xml | 2738 +++- lib/Zend/Locale/Data/hr_HR.xml | 6 +- lib/Zend/Locale/Data/hu.xml | 446 +- lib/Zend/Locale/Data/hu_HU.xml | 6 +- lib/Zend/Locale/Data/hy.xml | 35 +- lib/Zend/Locale/Data/hy_AM.xml | 6 +- lib/Zend/Locale/Data/hy_AM_REVISED.xml | 6 +- lib/Zend/Locale/Data/ia.xml | 19 +- lib/Zend/Locale/Data/id.xml | 51 +- lib/Zend/Locale/Data/id_ID.xml | 6 +- lib/Zend/Locale/Data/ig.xml | 20 +- lib/Zend/Locale/Data/ig_NG.xml | 6 +- lib/Zend/Locale/Data/ii.xml | 13 +- lib/Zend/Locale/Data/ii_CN.xml | 6 +- lib/Zend/Locale/Data/in.xml | 14 +- lib/Zend/Locale/Data/is.xml | 240 +- lib/Zend/Locale/Data/is_IS.xml | 6 +- lib/Zend/Locale/Data/it.xml | 176 +- lib/Zend/Locale/Data/it_CH.xml | 27 +- lib/Zend/Locale/Data/it_IT.xml | 6 +- lib/Zend/Locale/Data/iu.xml | 19 +- lib/Zend/Locale/Data/iw.xml | 16 +- lib/Zend/Locale/Data/ja.xml | 207 +- lib/Zend/Locale/Data/ja_JP.xml | 6 +- lib/Zend/Locale/Data/ka.xml | 33 +- lib/Zend/Locale/Data/ka_GE.xml | 6 +- lib/Zend/Locale/Data/kaj.xml | 21 +- lib/Zend/Locale/Data/kaj_NG.xml | 6 +- lib/Zend/Locale/Data/kam.xml | 20 +- lib/Zend/Locale/Data/kam_KE.xml | 6 +- lib/Zend/Locale/Data/kcg.xml | 23 +- lib/Zend/Locale/Data/kcg_NG.xml | 6 +- lib/Zend/Locale/Data/kfo.xml | 21 +- lib/Zend/Locale/Data/kfo_CI.xml | 6 +- lib/Zend/Locale/Data/kk.xml | 41 +- lib/Zend/Locale/Data/kk_Cyrl.xml | 6 +- lib/Zend/Locale/Data/kk_Cyrl_KZ.xml | 6 +- lib/Zend/Locale/Data/kk_KZ.xml | 6 +- lib/Zend/Locale/Data/kl.xml | 22 +- lib/Zend/Locale/Data/kl_GL.xml | 6 +- lib/Zend/Locale/Data/km.xml | 98 +- lib/Zend/Locale/Data/km_KH.xml | 6 +- lib/Zend/Locale/Data/kn.xml | 920 +- lib/Zend/Locale/Data/kn_IN.xml | 6 +- lib/Zend/Locale/Data/ko.xml | 727 +- lib/Zend/Locale/Data/ko_KR.xml | 6 +- lib/Zend/Locale/Data/kok.xml | 19 +- lib/Zend/Locale/Data/kok_IN.xml | 6 +- lib/Zend/Locale/Data/kpe.xml | 26 +- lib/Zend/Locale/Data/kpe_GN.xml | 13 +- lib/Zend/Locale/Data/kpe_LR.xml | 6 +- lib/Zend/Locale/Data/ku.xml | 31 +- lib/Zend/Locale/Data/ku_Arab.xml | 6 +- lib/Zend/Locale/Data/ku_Arab_IQ.xml | 11 + lib/Zend/Locale/Data/ku_Arab_IR.xml | 11 + lib/Zend/Locale/Data/ku_Arab_SY.xml | 11 + lib/Zend/Locale/Data/ku_IQ.xml | 11 + lib/Zend/Locale/Data/ku_IR.xml | 11 + lib/Zend/Locale/Data/ku_Latn.xml | 10 +- lib/Zend/Locale/Data/ku_Latn_TR.xml | 6 +- lib/Zend/Locale/Data/ku_SY.xml | 11 + lib/Zend/Locale/Data/ku_TR.xml | 6 +- lib/Zend/Locale/Data/kw.xml | 21 +- lib/Zend/Locale/Data/kw_GB.xml | 6 +- lib/Zend/Locale/Data/ky.xml | 19 +- lib/Zend/Locale/Data/ky_KG.xml | 6 +- lib/Zend/Locale/Data/likelySubtags.xml | 463 + lib/Zend/Locale/Data/ln.xml | 25 +- lib/Zend/Locale/Data/ln_CD.xml | 6 +- lib/Zend/Locale/Data/ln_CG.xml | 6 +- lib/Zend/Locale/Data/lo.xml | 86 +- lib/Zend/Locale/Data/lo_LA.xml | 6 +- lib/Zend/Locale/Data/lt.xml | 986 +- lib/Zend/Locale/Data/lt_LT.xml | 6 +- lib/Zend/Locale/Data/lv.xml | 921 +- lib/Zend/Locale/Data/lv_LV.xml | 6 +- lib/Zend/Locale/Data/metazoneInfo.xml | 1404 ++ lib/Zend/Locale/Data/mk.xml | 1424 +- lib/Zend/Locale/Data/mk_MK.xml | 6 +- lib/Zend/Locale/Data/ml.xml | 501 +- lib/Zend/Locale/Data/ml_IN.xml | 6 +- lib/Zend/Locale/Data/mn.xml | 24 +- lib/Zend/Locale/Data/mn_CN.xml | 6 +- lib/Zend/Locale/Data/mn_Cyrl.xml | 6 +- lib/Zend/Locale/Data/mn_Cyrl_MN.xml | 6 +- lib/Zend/Locale/Data/mn_MN.xml | 6 +- lib/Zend/Locale/Data/mn_Mong.xml | 6 +- lib/Zend/Locale/Data/mn_Mong_CN.xml | 6 +- lib/Zend/Locale/Data/mo.xml | 16 +- lib/Zend/Locale/Data/mr.xml | 1133 +- lib/Zend/Locale/Data/mr_IN.xml | 6 +- lib/Zend/Locale/Data/ms.xml | 22 +- lib/Zend/Locale/Data/ms_BN.xml | 27 +- lib/Zend/Locale/Data/ms_MY.xml | 6 +- lib/Zend/Locale/Data/mt.xml | 46 +- lib/Zend/Locale/Data/mt_MT.xml | 6 +- lib/Zend/Locale/Data/my.xml | 62 +- lib/Zend/Locale/Data/my_MM.xml | 6 +- lib/Zend/Locale/Data/nb.xml | 542 +- lib/Zend/Locale/Data/nb_NO.xml | 6 +- lib/Zend/Locale/Data/nds.xml | 1149 ++ lib/Zend/Locale/Data/nds_DE.xml | 10 + lib/Zend/Locale/Data/ne.xml | 35 +- lib/Zend/Locale/Data/ne_IN.xml | 6 +- lib/Zend/Locale/Data/ne_NP.xml | 6 +- lib/Zend/Locale/Data/nl.xml | 292 +- lib/Zend/Locale/Data/nl_BE.xml | 9 +- lib/Zend/Locale/Data/nl_NL.xml | 6 +- lib/Zend/Locale/Data/nn.xml | 1301 +- lib/Zend/Locale/Data/nn_NO.xml | 6 +- lib/Zend/Locale/Data/no.xml | 16 +- lib/Zend/Locale/Data/nr.xml | 25 +- lib/Zend/Locale/Data/nr_ZA.xml | 6 +- lib/Zend/Locale/Data/nso.xml | 25 +- lib/Zend/Locale/Data/nso_ZA.xml | 6 +- lib/Zend/Locale/Data/numberingSystems.xml | 42 + lib/Zend/Locale/Data/ny.xml | 20 +- lib/Zend/Locale/Data/ny_MW.xml | 6 +- lib/Zend/Locale/Data/oc.xml | 280 + lib/Zend/Locale/Data/oc_FR.xml | 10 + lib/Zend/Locale/Data/om.xml | 26 +- lib/Zend/Locale/Data/om_ET.xml | 6 +- lib/Zend/Locale/Data/om_KE.xml | 13 +- lib/Zend/Locale/Data/or.xml | 1010 +- lib/Zend/Locale/Data/or_IN.xml | 6 +- lib/Zend/Locale/Data/pa.xml | 53 +- lib/Zend/Locale/Data/pa_Arab.xml | 12 +- lib/Zend/Locale/Data/pa_Arab_PK.xml | 6 +- lib/Zend/Locale/Data/pa_Guru.xml | 6 +- lib/Zend/Locale/Data/pa_Guru_IN.xml | 6 +- lib/Zend/Locale/Data/pa_IN.xml | 6 +- lib/Zend/Locale/Data/pa_PK.xml | 6 +- lib/Zend/Locale/Data/pl.xml | 223 +- lib/Zend/Locale/Data/pl_PL.xml | 6 +- lib/Zend/Locale/Data/plurals.xml | 76 - lib/Zend/Locale/Data/postalCodeData.xml | 166 + lib/Zend/Locale/Data/ps.xml | 39 +- lib/Zend/Locale/Data/ps_AF.xml | 6 +- lib/Zend/Locale/Data/pt.xml | 941 +- lib/Zend/Locale/Data/pt_BR.xml | 6 +- lib/Zend/Locale/Data/pt_PT.xml | 155 +- lib/Zend/Locale/Data/ro.xml | 915 +- lib/Zend/Locale/Data/ro_MD.xml | 14 +- lib/Zend/Locale/Data/ro_RO.xml | 6 +- lib/Zend/Locale/Data/root.xml | 738 +- lib/Zend/Locale/Data/ru.xml | 273 +- lib/Zend/Locale/Data/ru_RU.xml | 6 +- lib/Zend/Locale/Data/ru_UA.xml | 33 +- lib/Zend/Locale/Data/rw.xml | 19 +- lib/Zend/Locale/Data/rw_RW.xml | 6 +- lib/Zend/Locale/Data/sa.xml | 125 +- lib/Zend/Locale/Data/sa_IN.xml | 6 +- lib/Zend/Locale/Data/se.xml | 167 +- lib/Zend/Locale/Data/se_FI.xml | 11 +- lib/Zend/Locale/Data/se_NO.xml | 6 +- lib/Zend/Locale/Data/sh.xml | 6 +- lib/Zend/Locale/Data/sh_BA.xml | 6 +- lib/Zend/Locale/Data/sh_CS.xml | 6 +- lib/Zend/Locale/Data/sh_YU.xml | 6 +- lib/Zend/Locale/Data/si.xml | 23 +- lib/Zend/Locale/Data/si_LK.xml | 6 +- lib/Zend/Locale/Data/sid.xml | 24 +- lib/Zend/Locale/Data/sid_ET.xml | 6 +- lib/Zend/Locale/Data/sk.xml | 759 +- lib/Zend/Locale/Data/sk_SK.xml | 6 +- lib/Zend/Locale/Data/sl.xml | 1584 +- lib/Zend/Locale/Data/sl_SI.xml | 6 +- lib/Zend/Locale/Data/so.xml | 44 +- lib/Zend/Locale/Data/so_DJ.xml | 16 +- lib/Zend/Locale/Data/so_ET.xml | 16 +- lib/Zend/Locale/Data/so_KE.xml | 13 +- lib/Zend/Locale/Data/so_SO.xml | 6 +- lib/Zend/Locale/Data/sq.xml | 52 +- lib/Zend/Locale/Data/sq_AL.xml | 6 +- lib/Zend/Locale/Data/sr.xml | 4164 ++++- lib/Zend/Locale/Data/sr_BA.xml | 6 +- lib/Zend/Locale/Data/sr_CS.xml | 6 +- lib/Zend/Locale/Data/sr_Cyrl.xml | 6 +- lib/Zend/Locale/Data/sr_Cyrl_BA.xml | 8 +- lib/Zend/Locale/Data/sr_Cyrl_CS.xml | 6 +- lib/Zend/Locale/Data/sr_Cyrl_ME.xml | 6 +- lib/Zend/Locale/Data/sr_Cyrl_RS.xml | 6 +- lib/Zend/Locale/Data/sr_Cyrl_YU.xml | 6 +- lib/Zend/Locale/Data/sr_Latn.xml | 4117 ++++- lib/Zend/Locale/Data/sr_Latn_BA.xml | 6 +- lib/Zend/Locale/Data/sr_Latn_CS.xml | 6 +- lib/Zend/Locale/Data/sr_Latn_ME.xml | 13 +- lib/Zend/Locale/Data/sr_Latn_RS.xml | 7 +- lib/Zend/Locale/Data/sr_Latn_YU.xml | 6 +- lib/Zend/Locale/Data/sr_ME.xml | 6 +- lib/Zend/Locale/Data/sr_RS.xml | 6 +- lib/Zend/Locale/Data/sr_YU.xml | 6 +- lib/Zend/Locale/Data/ss.xml | 23 +- lib/Zend/Locale/Data/ss_SZ.xml | 7 +- lib/Zend/Locale/Data/ss_ZA.xml | 6 +- lib/Zend/Locale/Data/st.xml | 25 +- lib/Zend/Locale/Data/st_LS.xml | 13 +- lib/Zend/Locale/Data/st_ZA.xml | 6 +- lib/Zend/Locale/Data/supplementalData.xml | 4284 ++--- lib/Zend/Locale/Data/sv.xml | 802 +- lib/Zend/Locale/Data/sv_FI.xml | 15 +- lib/Zend/Locale/Data/sv_SE.xml | 6 +- lib/Zend/Locale/Data/sw.xml | 24 +- lib/Zend/Locale/Data/sw_KE.xml | 6 +- lib/Zend/Locale/Data/sw_TZ.xml | 6 +- lib/Zend/Locale/Data/syr.xml | 108 +- lib/Zend/Locale/Data/syr_SY.xml | 6 +- lib/Zend/Locale/Data/ta.xml | 794 +- lib/Zend/Locale/Data/ta_IN.xml | 6 +- lib/Zend/Locale/Data/te.xml | 965 +- lib/Zend/Locale/Data/te_IN.xml | 6 +- lib/Zend/Locale/Data/telephoneCodeData.xml | 8 +- lib/Zend/Locale/Data/tg.xml | 62 +- lib/Zend/Locale/Data/tg_Cyrl.xml | 6 +- lib/Zend/Locale/Data/tg_Cyrl_TJ.xml | 6 +- lib/Zend/Locale/Data/tg_TJ.xml | 6 +- lib/Zend/Locale/Data/th.xml | 1445 +- lib/Zend/Locale/Data/th_TH.xml | 6 +- lib/Zend/Locale/Data/ti.xml | 27 +- lib/Zend/Locale/Data/ti_ER.xml | 37 +- lib/Zend/Locale/Data/ti_ET.xml | 6 +- lib/Zend/Locale/Data/tig.xml | 28 +- lib/Zend/Locale/Data/tig_ER.xml | 6 +- lib/Zend/Locale/Data/tl.xml | 16 +- lib/Zend/Locale/Data/tn.xml | 27 +- lib/Zend/Locale/Data/tn_ZA.xml | 6 +- lib/Zend/Locale/Data/to.xml | 36 +- lib/Zend/Locale/Data/to_TO.xml | 6 +- lib/Zend/Locale/Data/tr.xml | 711 +- lib/Zend/Locale/Data/tr_TR.xml | 6 +- lib/Zend/Locale/Data/trv.xml | 561 + lib/Zend/Locale/Data/trv_TW.xml | 10 + lib/Zend/Locale/Data/ts.xml | 93 +- lib/Zend/Locale/Data/ts_ZA.xml | 6 +- lib/Zend/Locale/Data/tt.xml | 124 +- lib/Zend/Locale/Data/tt_RU.xml | 6 +- lib/Zend/Locale/Data/ug.xml | 160 +- lib/Zend/Locale/Data/ug_Arab.xml | 7 +- lib/Zend/Locale/Data/ug_Arab_CN.xml | 6 +- lib/Zend/Locale/Data/ug_CN.xml | 6 +- lib/Zend/Locale/Data/uk.xml | 268 +- lib/Zend/Locale/Data/uk_UA.xml | 6 +- lib/Zend/Locale/Data/ur.xml | 82 +- lib/Zend/Locale/Data/ur_IN.xml | 7 +- lib/Zend/Locale/Data/ur_PK.xml | 6 +- lib/Zend/Locale/Data/uz.xml | 45 +- lib/Zend/Locale/Data/uz_AF.xml | 6 +- lib/Zend/Locale/Data/uz_Arab.xml | 17 +- lib/Zend/Locale/Data/uz_Arab_AF.xml | 6 +- lib/Zend/Locale/Data/uz_Cyrl.xml | 6 +- lib/Zend/Locale/Data/uz_Cyrl_UZ.xml | 6 +- lib/Zend/Locale/Data/uz_Latn.xml | 43 +- lib/Zend/Locale/Data/uz_Latn_UZ.xml | 6 +- lib/Zend/Locale/Data/uz_UZ.xml | 6 +- lib/Zend/Locale/Data/ve.xml | 57 +- lib/Zend/Locale/Data/ve_ZA.xml | 6 +- lib/Zend/Locale/Data/vi.xml | 107 +- lib/Zend/Locale/Data/vi_VN.xml | 6 +- lib/Zend/Locale/Data/wal.xml | 51 +- lib/Zend/Locale/Data/wal_ET.xml | 6 +- lib/Zend/Locale/Data/wo.xml | 169 +- lib/Zend/Locale/Data/wo_Latn.xml | 6 +- lib/Zend/Locale/Data/wo_Latn_SN.xml | 6 +- lib/Zend/Locale/Data/wo_SN.xml | 6 +- lib/Zend/Locale/Data/xh.xml | 99 +- lib/Zend/Locale/Data/xh_ZA.xml | 6 +- lib/Zend/Locale/Data/yo.xml | 112 +- lib/Zend/Locale/Data/yo_NG.xml | 6 +- lib/Zend/Locale/Data/zh.xml | 403 +- lib/Zend/Locale/Data/zh_CN.xml | 6 +- lib/Zend/Locale/Data/zh_HK.xml | 6 +- lib/Zend/Locale/Data/zh_Hans.xml | 6 +- lib/Zend/Locale/Data/zh_Hans_CN.xml | 6 +- lib/Zend/Locale/Data/zh_Hans_HK.xml | 13 +- lib/Zend/Locale/Data/zh_Hans_MO.xml | 6 +- lib/Zend/Locale/Data/zh_Hans_SG.xml | 23 +- lib/Zend/Locale/Data/zh_Hant.xml | 449 +- lib/Zend/Locale/Data/zh_Hant_HK.xml | 38 +- lib/Zend/Locale/Data/zh_Hant_MO.xml | 35 +- lib/Zend/Locale/Data/zh_Hant_TW.xml | 6 +- lib/Zend/Locale/Data/zh_MO.xml | 6 +- lib/Zend/Locale/Data/zh_SG.xml | 6 +- lib/Zend/Locale/Data/zh_TW.xml | 6 +- lib/Zend/Locale/Data/zu.xml | 55 +- lib/Zend/Locale/Data/zu_ZA.xml | 6 +- lib/Zend/Locale/Exception.php | 6 +- lib/Zend/Locale/Format.php | 302 +- lib/Zend/Locale/Math.php | 23 +- lib/Zend/Locale/Math/Exception.php | 10 +- lib/Zend/Locale/Math/PhpMath.php | 20 +- lib/Zend/Log.php | 8 +- lib/Zend/Log/Exception.php | 8 +- lib/Zend/Log/Filter/Interface.php | 8 +- lib/Zend/Log/Filter/Message.php | 8 +- lib/Zend/Log/Filter/Priority.php | 8 +- lib/Zend/Log/Filter/Suppress.php | 8 +- lib/Zend/Log/Formatter/Firebug.php | 5 +- lib/Zend/Log/Formatter/Interface.php | 8 +- lib/Zend/Log/Formatter/Simple.php | 8 +- lib/Zend/Log/Formatter/Xml.php | 8 +- lib/Zend/Log/Writer/Abstract.php | 8 +- lib/Zend/Log/Writer/Db.php | 8 +- lib/Zend/Log/Writer/Firebug.php | 5 +- lib/Zend/Log/Writer/Mail.php | 37 +- lib/Zend/Log/Writer/Mock.php | 8 +- lib/Zend/Log/Writer/Null.php | 8 +- lib/Zend/Log/Writer/Stream.php | 8 +- lib/Zend/Log/Writer/Syslog.php | 175 + lib/Zend/Mail.php | 19 +- lib/Zend/Mail/Exception.php | 6 +- lib/Zend/Mail/Message.php | 6 +- lib/Zend/Mail/Message/File.php | 6 +- lib/Zend/Mail/Message/Interface.php | 6 +- lib/Zend/Mail/Part.php | 6 +- lib/Zend/Mail/Part/File.php | 6 +- lib/Zend/Mail/Part/Interface.php | 6 +- lib/Zend/Mail/Protocol/Abstract.php | 8 +- lib/Zend/Mail/Protocol/Exception.php | 6 +- lib/Zend/Mail/Protocol/Imap.php | 6 +- lib/Zend/Mail/Protocol/Pop3.php | 6 +- lib/Zend/Mail/Protocol/Smtp.php | 6 +- lib/Zend/Mail/Protocol/Smtp/Auth/Crammd5.php | 6 +- lib/Zend/Mail/Protocol/Smtp/Auth/Login.php | 6 +- lib/Zend/Mail/Protocol/Smtp/Auth/Plain.php | 6 +- lib/Zend/Mail/Storage.php | 6 +- lib/Zend/Mail/Storage/Abstract.php | 6 +- lib/Zend/Mail/Storage/Exception.php | 6 +- lib/Zend/Mail/Storage/Folder.php | 6 +- lib/Zend/Mail/Storage/Folder/Interface.php | 6 +- lib/Zend/Mail/Storage/Folder/Maildir.php | 6 +- lib/Zend/Mail/Storage/Folder/Mbox.php | 6 +- lib/Zend/Mail/Storage/Imap.php | 6 +- lib/Zend/Mail/Storage/Maildir.php | 6 +- lib/Zend/Mail/Storage/Mbox.php | 6 +- lib/Zend/Mail/Storage/Pop3.php | 6 +- lib/Zend/Mail/Storage/Writable/Interface.php | 6 +- lib/Zend/Mail/Storage/Writable/Maildir.php | 6 +- lib/Zend/Mail/Transport/Abstract.php | 8 +- lib/Zend/Mail/Transport/Exception.php | 6 +- lib/Zend/Mail/Transport/Sendmail.php | 6 +- lib/Zend/Mail/Transport/Smtp.php | 6 +- lib/Zend/Measure/Abstract.php | 114 +- lib/Zend/Measure/Acceleration.php | 6 +- lib/Zend/Measure/Angle.php | 6 +- lib/Zend/Measure/Area.php | 6 +- lib/Zend/Measure/Binary.php | 6 +- lib/Zend/Measure/Capacitance.php | 6 +- lib/Zend/Measure/Cooking/Volume.php | 6 +- lib/Zend/Measure/Cooking/Weight.php | 6 +- lib/Zend/Measure/Current.php | 6 +- lib/Zend/Measure/Density.php | 6 +- lib/Zend/Measure/Energy.php | 6 +- lib/Zend/Measure/Exception.php | 6 +- lib/Zend/Measure/Flow/Mass.php | 6 +- lib/Zend/Measure/Flow/Mole.php | 6 +- lib/Zend/Measure/Flow/Volume.php | 6 +- lib/Zend/Measure/Force.php | 6 +- lib/Zend/Measure/Frequency.php | 6 +- lib/Zend/Measure/Illumination.php | 6 +- lib/Zend/Measure/Length.php | 6 +- lib/Zend/Measure/Lightness.php | 6 +- lib/Zend/Measure/Number.php | 18 +- lib/Zend/Measure/Power.php | 6 +- lib/Zend/Measure/Pressure.php | 6 +- lib/Zend/Measure/Speed.php | 6 +- lib/Zend/Measure/Temperature.php | 6 +- lib/Zend/Measure/Time.php | 6 +- lib/Zend/Measure/Torque.php | 6 +- lib/Zend/Measure/Viscosity/Dynamic.php | 6 +- lib/Zend/Measure/Viscosity/Kinematic.php | 6 +- lib/Zend/Measure/Volume.php | 6 +- lib/Zend/Measure/Weight.php | 6 +- lib/Zend/Memory.php | 5 +- lib/Zend/Memory/AccessController.php | 5 +- lib/Zend/Memory/Container.php | 5 +- lib/Zend/Memory/Container/Interface.php | 5 +- lib/Zend/Memory/Container/Locked.php | 5 +- lib/Zend/Memory/Container/Movable.php | 5 +- lib/Zend/Memory/Exception.php | 7 +- lib/Zend/Memory/Manager.php | 7 +- lib/Zend/Memory/Value.php | 6 +- lib/Zend/Mime.php | 8 +- lib/Zend/Mime/Decode.php | 5 +- lib/Zend/Mime/Exception.php | 5 +- lib/Zend/Mime/Message.php | 5 +- lib/Zend/Mime/Part.php | 5 +- lib/Zend/Navigation.php | 7 +- lib/Zend/Navigation/Container.php | 7 +- lib/Zend/Navigation/Exception.php | 7 +- lib/Zend/Navigation/Page.php | 5 +- lib/Zend/Navigation/Page/Mvc.php | 7 +- lib/Zend/Navigation/Page/Uri.php | 7 +- lib/Zend/OpenId.php | 6 +- lib/Zend/OpenId/Consumer.php | 6 +- lib/Zend/OpenId/Consumer/Storage.php | 6 +- lib/Zend/OpenId/Consumer/Storage/File.php | 6 +- lib/Zend/OpenId/Exception.php | 6 +- lib/Zend/OpenId/Extension.php | 6 +- lib/Zend/OpenId/Extension/Sreg.php | 6 +- lib/Zend/OpenId/Provider.php | 6 +- lib/Zend/OpenId/Provider/Storage.php | 6 +- lib/Zend/OpenId/Provider/Storage/File.php | 6 +- lib/Zend/OpenId/Provider/User.php | 6 +- lib/Zend/OpenId/Provider/User/Session.php | 6 +- lib/Zend/Paginator.php | 116 +- lib/Zend/Paginator/Adapter/Array.php | 6 +- lib/Zend/Paginator/Adapter/DbSelect.php | 9 +- lib/Zend/Paginator/Adapter/DbTableSelect.php | 6 +- lib/Zend/Paginator/Adapter/Interface.php | 6 +- lib/Zend/Paginator/Adapter/Iterator.php | 6 +- lib/Zend/Paginator/Adapter/Null.php | 6 +- lib/Zend/Paginator/AdapterAggregate.php | 40 + lib/Zend/Paginator/Exception.php | 6 +- lib/Zend/Paginator/ScrollingStyle/All.php | 6 +- lib/Zend/Paginator/ScrollingStyle/Elastic.php | 6 +- .../Paginator/ScrollingStyle/Interface.php | 6 +- lib/Zend/Paginator/ScrollingStyle/Jumping.php | 6 +- lib/Zend/Paginator/ScrollingStyle/Sliding.php | 6 +- lib/Zend/Pdf.php | 576 +- lib/Zend/Pdf/Action.php | 398 + lib/Zend/Pdf/Action/GoTo.php | 114 + lib/Zend/Pdf/Action/GoTo3DView.php | 39 + lib/Zend/Pdf/Action/GoToE.php | 38 + lib/Zend/Pdf/Action/GoToR.php | 38 + lib/Zend/Pdf/Action/Hide.php | 39 + lib/Zend/Pdf/Action/ImportData.php | 39 + lib/Zend/Pdf/Action/JavaScript.php | 39 + lib/Zend/Pdf/Action/Launch.php | 38 + lib/Zend/Pdf/Action/Movie.php | 38 + lib/Zend/Pdf/Action/Named.php | 39 + lib/Zend/Pdf/Action/Rendition.php | 39 + lib/Zend/Pdf/Action/ResetForm.php | 39 + lib/Zend/Pdf/Action/SetOCGState.php | 39 + lib/Zend/Pdf/Action/Sound.php | 39 + lib/Zend/Pdf/Action/SubmitForm.php | 39 + lib/Zend/Pdf/Action/Thread.php | 38 + lib/Zend/Pdf/Action/Trans.php | 39 + lib/Zend/Pdf/Action/URI.php | 37 + lib/Zend/Pdf/Action/Unknown.php | 38 + lib/Zend/Pdf/Annotation.php | 230 + lib/Zend/Pdf/Annotation/FileAttachment.php | 93 + lib/Zend/Pdf/Annotation/Link.php | 156 + lib/Zend/Pdf/Annotation/Text.php | 87 + lib/Zend/Pdf/Cmap.php | 6 +- lib/Zend/Pdf/Cmap/ByteEncoding.php | 6 +- lib/Zend/Pdf/Cmap/ByteEncoding/Static.php | 6 +- lib/Zend/Pdf/Cmap/SegmentToDelta.php | 6 +- lib/Zend/Pdf/Cmap/TrimmedTable.php | 6 +- lib/Zend/Pdf/Color.php | 13 +- lib/Zend/Pdf/Color/Cmyk.php | 39 +- lib/Zend/Pdf/Color/GrayScale.php | 26 +- lib/Zend/Pdf/Color/Html.php | 15 +- lib/Zend/Pdf/Color/Rgb.php | 15 +- lib/Zend/Pdf/Destination.php | 113 + lib/Zend/Pdf/Destination/Explicit.php | 121 + lib/Zend/Pdf/Destination/Fit.php | 69 + lib/Zend/Pdf/Destination/FitBoundingBox.php | 69 + .../FitBoundingBoxHorizontally.php | 92 + .../Destination/FitBoundingBoxVertically.php | 93 + lib/Zend/Pdf/Destination/FitHorizontally.php | 92 + lib/Zend/Pdf/Destination/FitRectangle.php | 165 + lib/Zend/Pdf/Destination/FitVertically.php | 92 + lib/Zend/Pdf/Destination/Named.php | 97 + lib/Zend/Pdf/Destination/Unknown.php | 37 + lib/Zend/Pdf/Destination/Zoom.php | 171 + lib/Zend/Pdf/Element.php | 13 +- lib/Zend/Pdf/Element/Array.php | 53 +- lib/Zend/Pdf/Element/Boolean.php | 5 +- lib/Zend/Pdf/Element/Dictionary.php | 5 +- lib/Zend/Pdf/Element/Name.php | 5 +- lib/Zend/Pdf/Element/Null.php | 5 +- lib/Zend/Pdf/Element/Numeric.php | 8 +- lib/Zend/Pdf/Element/Object.php | 36 +- lib/Zend/Pdf/Element/Object/Stream.php | 5 +- lib/Zend/Pdf/Element/Reference.php | 46 +- lib/Zend/Pdf/Element/Reference/Context.php | 7 +- lib/Zend/Pdf/Element/Reference/Table.php | 5 +- lib/Zend/Pdf/Element/Stream.php | 5 +- lib/Zend/Pdf/Element/String.php | 5 +- lib/Zend/Pdf/Element/String/Binary.php | 5 +- lib/Zend/Pdf/ElementFactory.php | 58 +- lib/Zend/Pdf/ElementFactory/Interface.php | 16 +- lib/Zend/Pdf/ElementFactory/Proxy.php | 18 +- lib/Zend/Pdf/Exception.php | 6 +- lib/Zend/Pdf/FileParser.php | 6 +- lib/Zend/Pdf/FileParser/Font.php | 6 +- lib/Zend/Pdf/FileParser/Font/OpenType.php | 6 +- .../Pdf/FileParser/Font/OpenType/TrueType.php | 6 +- lib/Zend/Pdf/FileParser/Image.php | 18 +- lib/Zend/Pdf/FileParser/Image/Png.php | 6 +- lib/Zend/Pdf/FileParserDataSource.php | 6 +- lib/Zend/Pdf/FileParserDataSource/File.php | 6 +- lib/Zend/Pdf/FileParserDataSource/String.php | 6 +- lib/Zend/Pdf/Filter/Ascii85.php | 6 +- lib/Zend/Pdf/Filter/AsciiHex.php | 6 +- lib/Zend/Pdf/Filter/Compression.php | 6 +- lib/Zend/Pdf/Filter/Compression/Flate.php | 6 +- lib/Zend/Pdf/Filter/Compression/Lzw.php | 6 +- lib/Zend/Pdf/Filter/Interface.php | 6 +- lib/Zend/Pdf/Font.php | 6 +- lib/Zend/Pdf/Image.php | 6 +- lib/Zend/Pdf/NameTree.php | 154 + lib/Zend/Pdf/Outline.php | 376 + lib/Zend/Pdf/Outline/Created.php | 317 + lib/Zend/Pdf/Outline/Loaded.php | 462 + lib/Zend/Pdf/Page.php | 68 +- lib/Zend/Pdf/Parser.php | 36 +- lib/Zend/Pdf/PhpArray.php | 128 - .../RecursivelyIteratableObjectsContainer.php | 45 + lib/Zend/Pdf/Resource.php | 6 +- lib/Zend/Pdf/Resource/Font.php | 6 +- lib/Zend/Pdf/Resource/Font/CidFont.php | 6 +- .../Pdf/Resource/Font/CidFont/TrueType.php | 6 +- lib/Zend/Pdf/Resource/Font/Extracted.php | 63 +- lib/Zend/Pdf/Resource/Font/FontDescriptor.php | 6 +- lib/Zend/Pdf/Resource/Font/Simple.php | 6 +- lib/Zend/Pdf/Resource/Font/Simple/Parsed.php | 6 +- .../Resource/Font/Simple/Parsed/TrueType.php | 6 +- .../Pdf/Resource/Font/Simple/Standard.php | 6 +- .../Resource/Font/Simple/Standard/Courier.php | 6 +- .../Font/Simple/Standard/CourierBold.php | 6 +- .../Simple/Standard/CourierBoldOblique.php | 6 +- .../Font/Simple/Standard/CourierOblique.php | 6 +- .../Font/Simple/Standard/Helvetica.php | 6 +- .../Font/Simple/Standard/HelveticaBold.php | 6 +- .../Simple/Standard/HelveticaBoldOblique.php | 6 +- .../Font/Simple/Standard/HelveticaOblique.php | 6 +- .../Resource/Font/Simple/Standard/Symbol.php | 6 +- .../Font/Simple/Standard/TimesBold.php | 6 +- .../Font/Simple/Standard/TimesBoldItalic.php | 6 +- .../Font/Simple/Standard/TimesItalic.php | 6 +- .../Font/Simple/Standard/TimesRoman.php | 6 +- .../Font/Simple/Standard/ZapfDingbats.php | 6 +- lib/Zend/Pdf/Resource/Font/Type0.php | 6 +- lib/Zend/Pdf/Resource/Image.php | 6 +- lib/Zend/Pdf/Resource/Image/Jpeg.php | 6 +- lib/Zend/Pdf/Resource/Image/Png.php | 6 +- lib/Zend/Pdf/Resource/Image/Tiff.php | 6 +- lib/Zend/Pdf/Resource/ImageFactory.php | 6 +- lib/Zend/Pdf/StringParser.php | 25 +- lib/Zend/Pdf/Style.php | 6 +- lib/Zend/Pdf/Target.php | 76 + lib/Zend/Pdf/Trailer.php | 6 +- lib/Zend/Pdf/Trailer/Generator.php | 6 +- lib/Zend/Pdf/Trailer/Keeper.php | 6 +- lib/Zend/Pdf/UpdateInfoContainer.php | 6 +- lib/Zend/ProgressBar.php | 6 +- lib/Zend/ProgressBar/Adapter.php | 6 +- lib/Zend/ProgressBar/Adapter/Console.php | 6 +- lib/Zend/ProgressBar/Adapter/Exception.php | 6 +- lib/Zend/ProgressBar/Adapter/JsPull.php | 6 +- lib/Zend/ProgressBar/Adapter/JsPush.php | 6 +- lib/Zend/ProgressBar/Exception.php | 6 +- lib/Zend/Queue.php | 569 + lib/Zend/Queue/Adapter/Activemq.php | 333 + lib/Zend/Queue/Adapter/AdapterAbstract.php | 191 + lib/Zend/Queue/Adapter/AdapterInterface.php | 174 + lib/Zend/Queue/Adapter/Array.php | 350 + lib/Zend/Queue/Adapter/Db.php | 481 + lib/Zend/Queue/Adapter/Db/Message.php | 51 + lib/Zend/Queue/Adapter/Db/Queue.php | 51 + lib/Zend/Queue/Adapter/Db/queue.sql | 75 + lib/Zend/Queue/Adapter/Memcacheq.php | 405 + lib/Zend/Queue/Adapter/Null.php | 174 + lib/Zend/Queue/Adapter/PlatformJobQueue.php | 343 + lib/Zend/Queue/Exception.php | 35 + lib/Zend/Queue/Message.php | 230 + lib/Zend/Queue/Message/Iterator.php | 285 + lib/Zend/Queue/Message/PlatformJob.php | 194 + lib/Zend/Queue/Stomp/Client.php | 173 + lib/Zend/Queue/Stomp/Client/Connection.php | 280 + .../Stomp/Client/ConnectionInterface.php | 103 + lib/Zend/Queue/Stomp/Frame.php | 363 + lib/Zend/Queue/Stomp/FrameInterface.php | 154 + lib/Zend/Reflection/Class.php | 2 +- lib/Zend/Reflection/Docblock.php | 2 +- lib/Zend/Reflection/Docblock/Tag.php | 6 +- lib/Zend/Reflection/Docblock/Tag/Param.php | 2 +- lib/Zend/Reflection/Docblock/Tag/Return.php | 2 +- lib/Zend/Reflection/Exception.php | 2 +- lib/Zend/Reflection/Extension.php | 2 +- lib/Zend/Reflection/File.php | 3 +- lib/Zend/Reflection/Function.php | 2 +- lib/Zend/Reflection/Method.php | 19 +- lib/Zend/Reflection/Parameter.php | 2 +- lib/Zend/Reflection/Property.php | 2 +- lib/Zend/Registry.php | 6 +- lib/Zend/Rest/Client.php | 5 +- lib/Zend/Rest/Client/Exception.php | 5 +- lib/Zend/Rest/Client/Result.php | 5 +- lib/Zend/Rest/Client/Result/Exception.php | 13 +- lib/Zend/Rest/Controller.php | 68 + lib/Zend/Rest/Exception.php | 5 +- lib/Zend/Rest/Route.php | 348 + lib/Zend/Rest/Server.php | 7 +- lib/Zend/Rest/Server/Exception.php | 6 +- lib/Zend/Search/Exception.php | 5 +- lib/Zend/Search/Lucene.php | 32 +- lib/Zend/Search/Lucene/Analysis/Analyzer.php | 5 +- .../Lucene/Analysis/Analyzer/Common.php | 5 +- .../Lucene/Analysis/Analyzer/Common/Text.php | 5 +- .../Analyzer/Common/Text/CaseInsensitive.php | 5 +- .../Analysis/Analyzer/Common/TextNum.php | 5 +- .../Common/TextNum/CaseInsensitive.php | 5 +- .../Lucene/Analysis/Analyzer/Common/Utf8.php | 5 +- .../Analyzer/Common/Utf8/CaseInsensitive.php | 5 +- .../Analysis/Analyzer/Common/Utf8Num.php | 5 +- .../Common/Utf8Num/CaseInsensitive.php | 5 +- lib/Zend/Search/Lucene/Analysis/Token.php | 5 +- .../Search/Lucene/Analysis/TokenFilter.php | 5 +- .../Lucene/Analysis/TokenFilter/LowerCase.php | 5 +- .../Analysis/TokenFilter/LowerCaseUtf8.php | 5 +- .../Analysis/TokenFilter/ShortWords.php | 5 +- .../Lucene/Analysis/TokenFilter/StopWords.php | 5 +- lib/Zend/Search/Lucene/Document.php | 6 +- lib/Zend/Search/Lucene/Document/Docx.php | 5 +- lib/Zend/Search/Lucene/Document/Exception.php | 5 +- lib/Zend/Search/Lucene/Document/Html.php | 5 +- lib/Zend/Search/Lucene/Document/OpenXml.php | 5 +- lib/Zend/Search/Lucene/Document/Pptx.php | 5 +- lib/Zend/Search/Lucene/Document/Xlsx.php | 5 +- lib/Zend/Search/Lucene/Exception.php | 5 +- lib/Zend/Search/Lucene/FSM.php | 5 +- lib/Zend/Search/Lucene/FSMAction.php | 5 +- lib/Zend/Search/Lucene/Field.php | 5 +- .../Search/Lucene/Index/DictionaryLoader.php | 5 +- lib/Zend/Search/Lucene/Index/DocsFilter.php | 5 +- lib/Zend/Search/Lucene/Index/FieldInfo.php | 5 +- lib/Zend/Search/Lucene/Index/SegmentInfo.php | 5 +- .../Search/Lucene/Index/SegmentMerger.php | 5 +- .../Search/Lucene/Index/SegmentWriter.php | 5 +- .../Index/SegmentWriter/DocumentWriter.php | 5 +- .../Index/SegmentWriter/StreamWriter.php | 5 +- lib/Zend/Search/Lucene/Index/Term.php | 5 +- lib/Zend/Search/Lucene/Index/TermInfo.php | 5 +- .../Lucene/Index/TermsPriorityQueue.php | 5 +- .../Lucene/Index/TermsStream/Interface.php | 5 +- lib/Zend/Search/Lucene/Index/Writer.php | 5 +- lib/Zend/Search/Lucene/Interface.php | 5 +- lib/Zend/Search/Lucene/LockManager.php | 5 +- lib/Zend/Search/Lucene/MultiSearcher.php | 5 +- lib/Zend/Search/Lucene/PriorityQueue.php | 5 +- lib/Zend/Search/Lucene/Proxy.php | 5 +- .../Search/BooleanExpressionRecognizer.php | 5 +- .../Lucene/Search/Highlighter/Default.php | 5 +- .../Lucene/Search/Highlighter/Interface.php | 5 +- lib/Zend/Search/Lucene/Search/Query.php | 5 +- .../Search/Lucene/Search/Query/Boolean.php | 5 +- lib/Zend/Search/Lucene/Search/Query/Empty.php | 5 +- lib/Zend/Search/Lucene/Search/Query/Fuzzy.php | 5 +- .../Lucene/Search/Query/Insignificant.php | 5 +- .../Search/Lucene/Search/Query/MultiTerm.php | 5 +- .../Search/Lucene/Search/Query/Phrase.php | 5 +- .../Lucene/Search/Query/Preprocessing.php | 5 +- .../Search/Query/Preprocessing/Fuzzy.php | 5 +- .../Search/Query/Preprocessing/Phrase.php | 5 +- .../Search/Query/Preprocessing/Term.php | 5 +- lib/Zend/Search/Lucene/Search/Query/Range.php | 5 +- lib/Zend/Search/Lucene/Search/Query/Term.php | 5 +- .../Search/Lucene/Search/Query/Wildcard.php | 7 +- lib/Zend/Search/Lucene/Search/QueryEntry.php | 5 +- .../Lucene/Search/QueryEntry/Phrase.php | 5 +- .../Lucene/Search/QueryEntry/Subquery.php | 5 +- .../Search/Lucene/Search/QueryEntry/Term.php | 5 +- lib/Zend/Search/Lucene/Search/QueryHit.php | 5 +- lib/Zend/Search/Lucene/Search/QueryLexer.php | 5 +- lib/Zend/Search/Lucene/Search/QueryParser.php | 5 +- .../Lucene/Search/QueryParserContext.php | 5 +- .../Lucene/Search/QueryParserException.php | 5 +- lib/Zend/Search/Lucene/Search/QueryToken.php | 5 +- lib/Zend/Search/Lucene/Search/Similarity.php | 5 +- .../Lucene/Search/Similarity/Default.php | 5 +- lib/Zend/Search/Lucene/Search/Weight.php | 5 +- .../Search/Lucene/Search/Weight/Boolean.php | 5 +- .../Search/Lucene/Search/Weight/Empty.php | 5 +- .../Search/Lucene/Search/Weight/MultiTerm.php | 5 +- .../Search/Lucene/Search/Weight/Phrase.php | 5 +- lib/Zend/Search/Lucene/Search/Weight/Term.php | 5 +- lib/Zend/Search/Lucene/Storage/Directory.php | 5 +- .../Lucene/Storage/Directory/Filesystem.php | 5 +- lib/Zend/Search/Lucene/Storage/File.php | 5 +- .../Search/Lucene/Storage/File/Filesystem.php | 5 +- .../Search/Lucene/Storage/File/Memory.php | 5 +- .../Lucene/TermStreamsPriorityQueue.php | 5 +- lib/Zend/Server/Abstract.php | 6 +- lib/Zend/Server/Cache.php | 6 +- lib/Zend/Server/Definition.php | 6 +- lib/Zend/Server/Exception.php | 4 +- lib/Zend/Server/Interface.php | 6 +- lib/Zend/Server/Method/Callback.php | 6 +- lib/Zend/Server/Method/Definition.php | 6 +- lib/Zend/Server/Method/Parameter.php | 6 +- lib/Zend/Server/Method/Prototype.php | 6 +- lib/Zend/Server/Reflection.php | 6 +- lib/Zend/Server/Reflection/Class.php | 6 +- lib/Zend/Server/Reflection/Exception.php | 12 +- lib/Zend/Server/Reflection/Function.php | 6 +- .../Server/Reflection/Function/Abstract.php | 6 +- lib/Zend/Server/Reflection/Method.php | 6 +- lib/Zend/Server/Reflection/Node.php | 6 +- lib/Zend/Server/Reflection/Parameter.php | 6 +- lib/Zend/Server/Reflection/Prototype.php | 6 +- lib/Zend/Server/Reflection/ReturnValue.php | 6 +- lib/Zend/Service/Abstract.php | 6 +- lib/Zend/Service/Akismet.php | 6 +- lib/Zend/Service/Amazon.php | 120 +- lib/Zend/Service/Amazon/Abstract.php | 6 +- lib/Zend/Service/Amazon/Accessories.php | 6 +- lib/Zend/Service/Amazon/CustomerReview.php | 6 +- lib/Zend/Service/Amazon/Ec2.php | 4 +- lib/Zend/Service/Amazon/Ec2/Abstract.php | 33 +- .../Service/Amazon/Ec2/Availabilityzones.php | 4 +- lib/Zend/Service/Amazon/Ec2/CloudWatch.php | 350 + lib/Zend/Service/Amazon/Ec2/Ebs.php | 10 +- lib/Zend/Service/Amazon/Ec2/Elasticip.php | 4 +- lib/Zend/Service/Amazon/Ec2/Exception.php | 4 +- lib/Zend/Service/Amazon/Ec2/Image.php | 6 +- lib/Zend/Service/Amazon/Ec2/Instance.php | 108 +- .../Service/Amazon/Ec2/Instance/Reserved.php | 140 + .../Service/Amazon/Ec2/Instance/Windows.php | 192 + lib/Zend/Service/Amazon/Ec2/Keypair.php | 4 +- lib/Zend/Service/Amazon/Ec2/Region.php | 4 +- lib/Zend/Service/Amazon/Ec2/Response.php | 29 +- .../Service/Amazon/Ec2/Securitygroups.php | 4 +- lib/Zend/Service/Amazon/EditorialReview.php | 6 +- lib/Zend/Service/Amazon/Exception.php | 6 +- lib/Zend/Service/Amazon/Image.php | 6 +- lib/Zend/Service/Amazon/Item.php | 6 +- lib/Zend/Service/Amazon/ListmaniaList.php | 6 +- lib/Zend/Service/Amazon/Offer.php | 6 +- lib/Zend/Service/Amazon/OfferSet.php | 6 +- lib/Zend/Service/Amazon/Query.php | 6 +- lib/Zend/Service/Amazon/ResultSet.php | 6 +- lib/Zend/Service/Amazon/S3.php | 144 +- lib/Zend/Service/Amazon/S3/Exception.php | 6 +- lib/Zend/Service/Amazon/S3/Stream.php | 6 +- lib/Zend/Service/Amazon/SimilarProduct.php | 6 +- lib/Zend/Service/Amazon/Sqs.php | 436 + lib/Zend/Service/Amazon/Sqs/Exception.php | 38 + lib/Zend/Service/Audioscrobbler.php | 6 +- lib/Zend/Service/Delicious.php | 6 +- lib/Zend/Service/Delicious/Exception.php | 6 +- lib/Zend/Service/Delicious/Post.php | 6 +- lib/Zend/Service/Delicious/PostList.php | 6 +- lib/Zend/Service/Delicious/SimplePost.php | 6 +- lib/Zend/Service/Exception.php | 6 +- lib/Zend/Service/Flickr.php | 6 +- lib/Zend/Service/Flickr/Image.php | 6 +- lib/Zend/Service/Flickr/Result.php | 6 +- lib/Zend/Service/Flickr/ResultSet.php | 6 +- lib/Zend/Service/Nirvanix.php | 5 +- lib/Zend/Service/Nirvanix/Exception.php | 13 +- lib/Zend/Service/Nirvanix/Namespace/Base.php | 7 +- lib/Zend/Service/Nirvanix/Namespace/Imfs.php | 7 +- lib/Zend/Service/Nirvanix/Response.php | 5 +- lib/Zend/Service/ReCaptcha.php | 10 +- lib/Zend/Service/ReCaptcha/Exception.php | 12 +- lib/Zend/Service/ReCaptcha/MailHide.php | 6 +- .../Service/ReCaptcha/MailHide/Exception.php | 6 +- lib/Zend/Service/ReCaptcha/Response.php | 6 +- lib/Zend/Service/Simpy.php | 6 +- lib/Zend/Service/Simpy/Link.php | 6 +- lib/Zend/Service/Simpy/LinkQuery.php | 6 +- lib/Zend/Service/Simpy/LinkSet.php | 6 +- lib/Zend/Service/Simpy/Note.php | 6 +- lib/Zend/Service/Simpy/NoteSet.php | 6 +- lib/Zend/Service/Simpy/Tag.php | 6 +- lib/Zend/Service/Simpy/TagSet.php | 6 +- lib/Zend/Service/Simpy/Watchlist.php | 6 +- lib/Zend/Service/Simpy/WatchlistFilter.php | 6 +- lib/Zend/Service/Simpy/WatchlistFilterSet.php | 6 +- lib/Zend/Service/Simpy/WatchlistSet.php | 6 +- lib/Zend/Service/SlideShare.php | 6 +- lib/Zend/Service/SlideShare/Exception.php | 12 +- lib/Zend/Service/SlideShare/SlideShow.php | 6 +- lib/Zend/Service/StrikeIron.php | 6 +- lib/Zend/Service/StrikeIron/Base.php | 6 +- lib/Zend/Service/StrikeIron/Decorator.php | 6 +- lib/Zend/Service/StrikeIron/Exception.php | 14 +- .../Service/StrikeIron/SalesUseTaxBasic.php | 6 +- .../StrikeIron/USAddressVerification.php | 6 +- lib/Zend/Service/StrikeIron/ZipCodeInfo.php | 6 +- lib/Zend/Service/Technorati.php | 6 +- lib/Zend/Service/Technorati/Author.php | 6 +- .../Service/Technorati/BlogInfoResult.php | 6 +- lib/Zend/Service/Technorati/CosmosResult.php | 6 +- .../Service/Technorati/CosmosResultSet.php | 6 +- .../Service/Technorati/DailyCountsResult.php | 6 +- .../Technorati/DailyCountsResultSet.php | 6 +- lib/Zend/Service/Technorati/Exception.php | 6 +- lib/Zend/Service/Technorati/GetInfoResult.php | 6 +- lib/Zend/Service/Technorati/KeyInfoResult.php | 6 +- lib/Zend/Service/Technorati/Result.php | 6 +- lib/Zend/Service/Technorati/ResultSet.php | 6 +- lib/Zend/Service/Technorati/SearchResult.php | 6 +- .../Service/Technorati/SearchResultSet.php | 6 +- lib/Zend/Service/Technorati/TagResult.php | 6 +- lib/Zend/Service/Technorati/TagResultSet.php | 6 +- lib/Zend/Service/Technorati/TagsResult.php | 6 +- lib/Zend/Service/Technorati/TagsResultSet.php | 6 +- lib/Zend/Service/Technorati/Utils.php | 6 +- lib/Zend/Service/Technorati/Weblog.php | 6 +- lib/Zend/Service/Twitter.php | 12 +- lib/Zend/Service/Twitter/Exception.php | 13 +- lib/Zend/Service/Twitter/Search.php | 9 +- lib/Zend/Service/Yahoo.php | 6 +- lib/Zend/Service/Yahoo/Image.php | 6 +- lib/Zend/Service/Yahoo/ImageResult.php | 6 +- lib/Zend/Service/Yahoo/ImageResultSet.php | 6 +- lib/Zend/Service/Yahoo/InlinkDataResult.php | 6 +- .../Service/Yahoo/InlinkDataResultSet.php | 6 +- lib/Zend/Service/Yahoo/LocalResult.php | 6 +- lib/Zend/Service/Yahoo/LocalResultSet.php | 6 +- lib/Zend/Service/Yahoo/NewsResult.php | 6 +- lib/Zend/Service/Yahoo/NewsResultSet.php | 6 +- lib/Zend/Service/Yahoo/PageDataResult.php | 6 +- lib/Zend/Service/Yahoo/PageDataResultSet.php | 6 +- lib/Zend/Service/Yahoo/Result.php | 6 +- lib/Zend/Service/Yahoo/ResultSet.php | 6 +- lib/Zend/Service/Yahoo/VideoResult.php | 6 +- lib/Zend/Service/Yahoo/VideoResultSet.php | 6 +- lib/Zend/Service/Yahoo/WebResult.php | 6 +- lib/Zend/Service/Yahoo/WebResultSet.php | 6 +- lib/Zend/Session.php | 76 +- lib/Zend/Session/Abstract.php | 6 +- lib/Zend/Session/Exception.php | 6 +- lib/Zend/Session/Namespace.php | 25 +- lib/Zend/Session/SaveHandler/DbTable.php | 10 +- lib/Zend/Session/SaveHandler/Exception.php | 6 +- lib/Zend/Session/SaveHandler/Interface.php | 6 +- lib/Zend/Session/Validator/Abstract.php | 6 +- lib/Zend/Session/Validator/HttpUserAgent.php | 6 +- lib/Zend/Session/Validator/Interface.php | 6 +- lib/Zend/Soap/AutoDiscover.php | 189 +- lib/Zend/Soap/AutoDiscover/Exception.php | 5 +- lib/Zend/Soap/Client.php | 53 +- lib/Zend/Soap/Client/Common.php | 3 +- lib/Zend/Soap/Client/DotNet.php | 3 +- lib/Zend/Soap/Client/Exception.php | 6 +- lib/Zend/Soap/Client/Local.php | 3 +- lib/Zend/Soap/Server.php | 6 +- lib/Zend/Soap/Server/Exception.php | 6 +- lib/Zend/Soap/Wsdl.php | 75 +- lib/Zend/Soap/Wsdl/Exception.php | 4 +- lib/Zend/Soap/Wsdl/Strategy/Abstract.php | 8 +- lib/Zend/Soap/Wsdl/Strategy/AnyType.php | 6 +- .../Soap/Wsdl/Strategy/ArrayOfTypeComplex.php | 4 +- .../Wsdl/Strategy/ArrayOfTypeSequence.php | 31 +- lib/Zend/Soap/Wsdl/Strategy/Composite.php | 6 +- .../Soap/Wsdl/Strategy/DefaultComplexType.php | 6 +- lib/Zend/Soap/Wsdl/Strategy/Interface.php | 4 +- lib/Zend/Tag/Cloud.php | 6 +- lib/Zend/Tag/Cloud/Decorator/Cloud.php | 6 +- lib/Zend/Tag/Cloud/Decorator/Exception.php | 6 +- lib/Zend/Tag/Cloud/Decorator/HtmlCloud.php | 8 +- lib/Zend/Tag/Cloud/Decorator/HtmlTag.php | 6 +- lib/Zend/Tag/Cloud/Decorator/Tag.php | 6 +- lib/Zend/Tag/Cloud/Exception.php | 6 +- lib/Zend/Tag/Exception.php | 6 +- lib/Zend/Tag/Item.php | 6 +- lib/Zend/Tag/ItemList.php | 6 +- lib/Zend/Tag/Taggable.php | 6 +- lib/Zend/Test/DbAdapter.php | 316 + lib/Zend/Test/DbStatement.php | 381 + lib/Zend/Test/PHPUnit/Constraint/DomQuery.php | 72 +- .../Test/PHPUnit/Constraint/Exception.php | 28 +- lib/Zend/Test/PHPUnit/Constraint/Redirect.php | 52 +- .../PHPUnit/Constraint/ResponseHeader.php | 108 +- lib/Zend/Test/PHPUnit/ControllerTestCase.php | 249 +- lib/Zend/Test/PHPUnit/DatabaseTestCase.php | 151 + lib/Zend/Test/PHPUnit/Db/Connection.php | 149 + lib/Zend/Test/PHPUnit/Db/DataSet/DbRowset.php | 75 + lib/Zend/Test/PHPUnit/Db/DataSet/DbTable.php | 123 + .../PHPUnit/Db/DataSet/DbTableDataSet.php | 103 + .../Test/PHPUnit/Db/DataSet/QueryDataSet.php | 86 + .../Test/PHPUnit/Db/DataSet/QueryTable.php | 86 + lib/Zend/Test/PHPUnit/Db/Exception.php | 41 + lib/Zend/Test/PHPUnit/Db/Metadata/Generic.php | 164 + .../Test/PHPUnit/Db/Operation/DeleteAll.php | 69 + lib/Zend/Test/PHPUnit/Db/Operation/Insert.php | 92 + .../Test/PHPUnit/Db/Operation/Truncate.php | 98 + lib/Zend/Test/PHPUnit/Db/SimpleTester.php | 95 + lib/Zend/Text/Exception.php | 6 +- lib/Zend/Text/Figlet.php | 6 +- lib/Zend/Text/Figlet/Exception.php | 6 +- lib/Zend/Text/Figlet/zend-framework.flf | 2 +- lib/Zend/Text/MultiByte.php | 6 +- lib/Zend/Text/Table.php | 6 +- lib/Zend/Text/Table/Column.php | 6 +- lib/Zend/Text/Table/Decorator/Ascii.php | 6 +- lib/Zend/Text/Table/Decorator/Interface.php | 6 +- lib/Zend/Text/Table/Decorator/Unicode.php | 6 +- lib/Zend/Text/Table/Exception.php | 6 +- lib/Zend/Text/Table/Row.php | 6 +- lib/Zend/TimeSync.php | 6 +- lib/Zend/TimeSync/Exception.php | 6 +- lib/Zend/TimeSync/Ntp.php | 6 +- lib/Zend/TimeSync/Protocol.php | 6 +- lib/Zend/TimeSync/Sntp.php | 6 +- lib/Zend/Tool/Framework/Action/Base.php | 2 +- lib/Zend/Tool/Framework/Action/Exception.php | 2 +- lib/Zend/Tool/Framework/Action/Interface.php | 2 +- lib/Zend/Tool/Framework/Action/Repository.php | 2 +- lib/Zend/Tool/Framework/Client/Abstract.php | 19 +- lib/Zend/Tool/Framework/Client/Config.php | 105 + lib/Zend/Tool/Framework/Client/Console.php | 43 +- .../Client/Console/ArgumentParser.php | 2 +- .../Framework/Client/Console/HelpSystem.php | 2 +- .../Framework/Client/Console/Manifest.php | 2 +- .../Console/ResponseDecorator/Colorizer.php | 49 +- lib/Zend/Tool/Framework/Client/Exception.php | 2 +- .../Client/Interactive/InputHandler.php | 27 +- .../Client/Interactive/InputInterface.php | 27 +- .../Client/Interactive/InputRequest.php | 27 +- .../Client/Interactive/InputResponse.php | 25 + .../Client/Interactive/OutputInterface.php | 27 +- lib/Zend/Tool/Framework/Client/Request.php | 2 +- lib/Zend/Tool/Framework/Client/Response.php | 2 +- .../Response/ContentDecorator/Interface.php | 25 + .../Response/ContentDecorator/Separator.php | 2 +- lib/Zend/Tool/Framework/Client/Storage.php | 117 + .../Client/Storage/AdapterInterface.php | 42 + .../Framework/Client/Storage/Directory.php | 73 + lib/Zend/Tool/Framework/Exception.php | 2 +- lib/Zend/Tool/Framework/Loader/Abstract.php | 2 +- .../Framework/Loader/IncludePathLoader.php | 2 +- .../RecursiveFilterIterator.php | 2 +- .../Framework/Manifest/ActionManifestable.php | 2 +- .../Tool/Framework/Manifest/Exception.php | 2 +- .../Tool/Framework/Manifest/Indexable.php | 2 +- .../Tool/Framework/Manifest/Interface.php | 25 + .../Manifest/MetadataManifestable.php | 2 +- .../Manifest/ProviderManifestable.php | 2 +- .../Tool/Framework/Manifest/Repository.php | 2 +- lib/Zend/Tool/Framework/Metadata/Basic.php | 2 +- lib/Zend/Tool/Framework/Metadata/Dynamic.php | 53 +- .../Tool/Framework/Metadata/Interface.php | 2 +- lib/Zend/Tool/Framework/Metadata/Tool.php | 2 +- lib/Zend/Tool/Framework/Provider/Abstract.php | 2 +- .../Provider/DocblockManifestable.php | 25 + .../Tool/Framework/Provider/Exception.php | 2 +- .../Tool/Framework/Provider/Interactable.php | 25 + .../Tool/Framework/Provider/Interface.php | 2 +- .../Tool/Framework/Provider/Pretendable.php | 2 +- .../Tool/Framework/Provider/Repository.php | 5 +- .../Tool/Framework/Provider/Signature.php | 2 +- lib/Zend/Tool/Framework/Registry.php | 66 +- .../Framework/Registry/EnabledInterface.php | 2 +- .../Tool/Framework/Registry/Exception.php | 25 + .../Tool/Framework/Registry/Interface.php | 25 + .../Tool/Framework/System/Action/Create.php | 2 +- .../Tool/Framework/System/Action/Delete.php | 2 +- lib/Zend/Tool/Framework/System/Manifest.php | 2 +- .../Framework/System/Provider/Manifest.php | 2 +- .../Framework/System/Provider/Phpinfo.php | 27 +- .../Framework/System/Provider/Version.php | 23 + .../Tool/Project/Context/Content/Engine.php | 106 + .../Context/Content/Engine/CodeGenerator.php | 98 + .../Project/Context/Content/Engine/Phtml.php | 89 + lib/Zend/Tool/Project/Context/Exception.php | 27 +- .../Project/Context/Filesystem/Abstract.php | 2 +- .../Project/Context/Filesystem/Directory.php | 4 +- .../Tool/Project/Context/Filesystem/File.php | 2 +- lib/Zend/Tool/Project/Context/Interface.php | 23 + lib/Zend/Tool/Project/Context/Repository.php | 25 + .../Tool/Project/Context/System/Interface.php | 2 +- .../Context/System/NotOverwritable.php | 2 +- .../Context/System/ProjectDirectory.php | 2 +- .../Context/System/ProjectProfileFile.php | 2 +- .../System/ProjectProvidersDirectory.php | 2 +- .../Context/System/TopLevelRestrictable.php | 2 +- .../Tool/Project/Context/Zf/ActionMethod.php | 2 +- .../Tool/Project/Context/Zf/ApisDirectory.php | 2 +- .../Context/Zf/ApplicationConfigFile.php | 2 +- .../Context/Zf/ApplicationDirectory.php | 2 +- .../Tool/Project/Context/Zf/BootstrapFile.php | 2 +- .../Project/Context/Zf/CacheDirectory.php | 2 +- .../Tool/Project/Context/Zf/ConfigFile.php | 2 +- .../Project/Context/Zf/ConfigsDirectory.php | 2 +- .../Project/Context/Zf/ControllerFile.php | 19 +- .../Context/Zf/ControllersDirectory.php | 2 +- .../Tool/Project/Context/Zf/DataDirectory.php | 2 +- .../Project/Context/Zf/DbTableDirectory.php | 2 +- .../Tool/Project/Context/Zf/DbTableFile.php | 2 +- lib/Zend/Tool/Project/Context/Zf/FormFile.php | 2 +- .../Project/Context/Zf/FormsDirectory.php | 2 +- .../Tool/Project/Context/Zf/HtaccessFile.php | 4 +- .../Project/Context/Zf/LayoutsDirectory.php | 2 +- .../Project/Context/Zf/LibraryDirectory.php | 2 +- .../Project/Context/Zf/LocalesDirectory.php | 2 +- .../Tool/Project/Context/Zf/LogsDirectory.php | 2 +- .../Tool/Project/Context/Zf/ModelFile.php | 2 +- .../Project/Context/Zf/ModelsDirectory.php | 2 +- .../Project/Context/Zf/ModuleDirectory.php | 2 +- .../Project/Context/Zf/ModulesDirectory.php | 2 +- .../Context/Zf/ProjectProviderFile.php | 2 +- .../Project/Context/Zf/PublicDirectory.php | 2 +- .../Context/Zf/PublicImagesDirectory.php | 2 +- .../Project/Context/Zf/PublicIndexFile.php | 2 +- .../Context/Zf/PublicScriptsDirectory.php | 2 +- .../Context/Zf/PublicStylesheetsDirectory.php | 2 +- .../Context/Zf/SearchIndexesDirectory.php | 2 +- .../Project/Context/Zf/SessionsDirectory.php | 2 +- .../Project/Context/Zf/TemporaryDirectory.php | 2 +- .../Zf/TestApplicationBootstrapFile.php | 2 +- .../Zf/TestApplicationControllerDirectory.php | 2 +- .../Zf/TestApplicationControllerFile.php | 2 +- .../Context/Zf/TestApplicationDirectory.php | 2 +- .../Context/Zf/TestLibraryBootstrapFile.php | 2 +- .../Context/Zf/TestLibraryDirectory.php | 2 +- .../Project/Context/Zf/TestLibraryFile.php | 2 +- .../Zf/TestLibraryNamespaceDirectory.php | 2 +- .../Context/Zf/TestPHPUnitConfigFile.php | 2 +- .../Project/Context/Zf/TestsDirectory.php | 2 +- .../Project/Context/Zf/UploadsDirectory.php | 2 +- .../Zf/ViewControllerScriptsDirectory.php | 2 +- .../Context/Zf/ViewFiltersDirectory.php | 2 +- .../Context/Zf/ViewHelpersDirectory.php | 2 +- .../Project/Context/Zf/ViewScriptFile.php | 37 +- .../Context/Zf/ViewScriptsDirectory.php | 2 +- .../Project/Context/Zf/ViewsDirectory.php | 2 +- .../Context/Zf/ZfStandardLibraryDirectory.php | 2 +- lib/Zend/Tool/Project/Exception.php | 2 +- lib/Zend/Tool/Project/Profile.php | 4 +- lib/Zend/Tool/Project/Profile/Exception.php | 2 +- .../Project/Profile/FileParser/Interface.php | 2 +- .../Tool/Project/Profile/FileParser/Xml.php | 2 +- .../Profile/Iterator/ContextFilter.php | 2 +- .../Iterator/EnabledResourceFilter.php | 2 +- lib/Zend/Tool/Project/Profile/Resource.php | 2 +- .../Project/Profile/Resource/Container.php | 2 +- .../Profile/Resource/SearchConstraints.php | 2 +- lib/Zend/Tool/Project/Provider/Abstract.php | 61 +- lib/Zend/Tool/Project/Provider/Action.php | 2 +- lib/Zend/Tool/Project/Provider/Controller.php | 2 +- lib/Zend/Tool/Project/Provider/Exception.php | 2 +- lib/Zend/Tool/Project/Provider/Form.php | 2 +- lib/Zend/Tool/Project/Provider/Manifest.php | 2 +- lib/Zend/Tool/Project/Provider/Model.php | 2 +- lib/Zend/Tool/Project/Provider/Module.php | 2 +- lib/Zend/Tool/Project/Provider/Profile.php | 2 +- lib/Zend/Tool/Project/Provider/Project.php | 38 +- .../Tool/Project/Provider/ProjectProvider.php | 196 +- lib/Zend/Tool/Project/Provider/Test.php | 2 +- lib/Zend/Tool/Project/Provider/View.php | 2 +- lib/Zend/Translate.php | 6 +- lib/Zend/Translate/Adapter.php | 107 +- lib/Zend/Translate/Adapter/Array.php | 6 +- lib/Zend/Translate/Adapter/Csv.php | 15 +- lib/Zend/Translate/Adapter/Gettext.php | 21 +- lib/Zend/Translate/Adapter/Ini.php | 8 +- lib/Zend/Translate/Adapter/Qt.php | 6 +- lib/Zend/Translate/Adapter/Tbx.php | 6 +- lib/Zend/Translate/Adapter/Tmx.php | 6 +- lib/Zend/Translate/Adapter/Xliff.php | 6 +- lib/Zend/Translate/Adapter/XmlTm.php | 6 +- lib/Zend/Translate/Exception.php | 6 +- lib/Zend/Translate/Plural.php | 177 + lib/Zend/Uri.php | 6 +- lib/Zend/Uri/Exception.php | 6 +- lib/Zend/Uri/Http.php | 6 +- lib/Zend/Validate.php | 86 +- lib/Zend/Validate/Abstract.php | 39 +- lib/Zend/Validate/Alnum.php | 28 +- lib/Zend/Validate/Alpha.php | 31 +- lib/Zend/Validate/Barcode.php | 6 +- lib/Zend/Validate/Barcode/Ean13.php | 18 +- lib/Zend/Validate/Barcode/UpcA.php | 19 +- lib/Zend/Validate/Between.php | 6 +- lib/Zend/Validate/Ccnum.php | 6 +- lib/Zend/Validate/Date.php | 51 +- lib/Zend/Validate/Db/Abstract.php | 34 +- lib/Zend/Validate/Db/NoRecordExists.php | 2 +- lib/Zend/Validate/Db/RecordExists.php | 6 +- lib/Zend/Validate/Digits.php | 33 +- lib/Zend/Validate/EmailAddress.php | 24 +- lib/Zend/Validate/Exception.php | 6 +- lib/Zend/Validate/File/Count.php | 6 +- lib/Zend/Validate/File/Crc32.php | 6 +- lib/Zend/Validate/File/ExcludeExtension.php | 6 +- lib/Zend/Validate/File/ExcludeMimeType.php | 6 +- lib/Zend/Validate/File/Exists.php | 6 +- lib/Zend/Validate/File/Extension.php | 6 +- lib/Zend/Validate/File/FilesSize.php | 6 +- lib/Zend/Validate/File/Hash.php | 6 +- lib/Zend/Validate/File/ImageSize.php | 6 +- lib/Zend/Validate/File/IsCompressed.php | 6 +- lib/Zend/Validate/File/IsImage.php | 6 +- lib/Zend/Validate/File/Md5.php | 6 +- lib/Zend/Validate/File/MimeType.php | 30 +- lib/Zend/Validate/File/NotExists.php | 6 +- lib/Zend/Validate/File/Sha1.php | 6 +- lib/Zend/Validate/File/Size.php | 6 +- lib/Zend/Validate/File/Upload.php | 6 +- lib/Zend/Validate/File/WordCount.php | 6 +- lib/Zend/Validate/Float.php | 44 +- lib/Zend/Validate/GreaterThan.php | 8 +- lib/Zend/Validate/Hex.php | 23 +- lib/Zend/Validate/Hostname.php | 43 +- lib/Zend/Validate/Hostname/Biz.php | 6 +- lib/Zend/Validate/Hostname/Cn.php | 6 +- lib/Zend/Validate/Hostname/Com.php | 11 +- lib/Zend/Validate/Hostname/Jp.php | 6 +- lib/Zend/Validate/Iban.php | 6 +- lib/Zend/Validate/Identical.php | 42 +- lib/Zend/Validate/InArray.php | 8 +- lib/Zend/Validate/Int.php | 46 +- lib/Zend/Validate/Interface.php | 6 +- lib/Zend/Validate/Ip.php | 23 +- lib/Zend/Validate/LessThan.php | 8 +- lib/Zend/Validate/NotEmpty.php | 21 +- lib/Zend/Validate/Regex.php | 25 +- lib/Zend/Validate/Sitemap/Changefreq.php | 9 +- lib/Zend/Validate/Sitemap/Lastmod.php | 7 +- lib/Zend/Validate/Sitemap/Loc.php | 7 +- lib/Zend/Validate/Sitemap/Priority.php | 7 +- lib/Zend/Validate/StringLength.php | 20 +- lib/Zend/Version.php | 8 +- lib/Zend/View.php | 5 +- lib/Zend/View/Abstract.php | 5 +- lib/Zend/View/Exception.php | 6 +- lib/Zend/View/Helper/Abstract.php | 6 +- lib/Zend/View/Helper/Action.php | 6 +- lib/Zend/View/Helper/BaseUrl.php | 116 + lib/Zend/View/Helper/Cycle.php | 451 +- lib/Zend/View/Helper/DeclareVars.php | 6 +- lib/Zend/View/Helper/Doctype.php | 7 +- lib/Zend/View/Helper/Fieldset.php | 6 +- lib/Zend/View/Helper/Form.php | 6 +- lib/Zend/View/Helper/FormButton.php | 5 +- lib/Zend/View/Helper/FormCheckbox.php | 37 +- lib/Zend/View/Helper/FormElement.php | 5 +- lib/Zend/View/Helper/FormErrors.php | 5 +- lib/Zend/View/Helper/FormFile.php | 5 +- lib/Zend/View/Helper/FormHidden.php | 5 +- lib/Zend/View/Helper/FormImage.php | 5 +- lib/Zend/View/Helper/FormLabel.php | 5 +- lib/Zend/View/Helper/FormMultiCheckbox.php | 5 +- lib/Zend/View/Helper/FormNote.php | 5 +- lib/Zend/View/Helper/FormPassword.php | 5 +- lib/Zend/View/Helper/FormRadio.php | 5 +- lib/Zend/View/Helper/FormReset.php | 5 +- lib/Zend/View/Helper/FormSelect.php | 5 +- lib/Zend/View/Helper/FormSubmit.php | 5 +- lib/Zend/View/Helper/FormText.php | 5 +- lib/Zend/View/Helper/FormTextarea.php | 5 +- lib/Zend/View/Helper/HeadLink.php | 7 +- lib/Zend/View/Helper/HeadMeta.php | 9 +- lib/Zend/View/Helper/HeadScript.php | 7 +- lib/Zend/View/Helper/HeadStyle.php | 7 +- lib/Zend/View/Helper/HeadTitle.php | 7 +- lib/Zend/View/Helper/HtmlElement.php | 6 +- lib/Zend/View/Helper/HtmlFlash.php | 6 +- lib/Zend/View/Helper/HtmlList.php | 5 +- lib/Zend/View/Helper/HtmlObject.php | 6 +- lib/Zend/View/Helper/HtmlPage.php | 6 +- lib/Zend/View/Helper/HtmlQuicktime.php | 6 +- lib/Zend/View/Helper/InlineScript.php | 7 +- lib/Zend/View/Helper/Interface.php | 6 +- lib/Zend/View/Helper/Json.php | 6 +- lib/Zend/View/Helper/Layout.php | 6 +- lib/Zend/View/Helper/Navigation.php | 7 +- .../View/Helper/Navigation/Breadcrumbs.php | 7 +- lib/Zend/View/Helper/Navigation/Helper.php | 7 +- .../View/Helper/Navigation/HelperAbstract.php | 7 +- lib/Zend/View/Helper/Navigation/Links.php | 7 +- lib/Zend/View/Helper/Navigation/Menu.php | 34 +- lib/Zend/View/Helper/Navigation/Sitemap.php | 7 +- lib/Zend/View/Helper/PaginationControl.php | 6 +- lib/Zend/View/Helper/Partial.php | 6 +- lib/Zend/View/Helper/Partial/Exception.php | 6 +- lib/Zend/View/Helper/PartialLoop.php | 6 +- lib/Zend/View/Helper/Placeholder.php | 7 +- .../View/Helper/Placeholder/Container.php | 7 +- .../Helper/Placeholder/Container/Abstract.php | 7 +- .../Placeholder/Container/Exception.php | 6 +- .../Placeholder/Container/Standalone.php | 7 +- lib/Zend/View/Helper/Placeholder/Registry.php | 7 +- .../Helper/Placeholder/Registry/Exception.php | 6 +- lib/Zend/View/Helper/RenderToPlaceholder.php | 6 +- lib/Zend/View/Helper/ServerUrl.php | 7 +- lib/Zend/View/Helper/Translate.php | 6 +- lib/Zend/View/Helper/Url.php | 6 +- lib/Zend/View/Interface.php | 5 +- lib/Zend/View/Stream.php | 5 +- lib/Zend/Wildfire/Channel/HttpHeaders.php | 19 +- lib/Zend/Wildfire/Channel/Interface.php | 5 +- lib/Zend/Wildfire/Exception.php | 6 +- lib/Zend/Wildfire/Plugin/FirePhp.php | 5 +- lib/Zend/Wildfire/Plugin/FirePhp/Message.php | 5 +- .../Wildfire/Plugin/FirePhp/TableMessage.php | 5 +- lib/Zend/Wildfire/Plugin/Interface.php | 5 +- lib/Zend/Wildfire/Protocol/JsonStream.php | 5 +- lib/Zend/XmlRpc/Client.php | 59 +- lib/Zend/XmlRpc/Client/Exception.php | 5 +- lib/Zend/XmlRpc/Client/FaultException.php | 5 +- lib/Zend/XmlRpc/Client/HttpException.php | 5 +- .../XmlRpc/Client/IntrospectException.php | 5 +- .../XmlRpc/Client/ServerIntrospection.php | 5 +- lib/Zend/XmlRpc/Client/ServerProxy.php | 5 +- lib/Zend/XmlRpc/Exception.php | 5 +- lib/Zend/XmlRpc/Fault.php | 5 +- lib/Zend/XmlRpc/Request.php | 8 +- lib/Zend/XmlRpc/Request/Http.php | 6 +- lib/Zend/XmlRpc/Request/Stdin.php | 6 +- lib/Zend/XmlRpc/Response.php | 6 +- lib/Zend/XmlRpc/Response/Http.php | 6 +- lib/Zend/XmlRpc/Server.php | 62 +- lib/Zend/XmlRpc/Server/Cache.php | 6 +- lib/Zend/XmlRpc/Server/Exception.php | 6 +- lib/Zend/XmlRpc/Server/Fault.php | 6 +- lib/Zend/XmlRpc/Server/System.php | 20 +- lib/Zend/XmlRpc/Value.php | 101 +- lib/Zend/XmlRpc/Value/Array.php | 6 +- lib/Zend/XmlRpc/Value/Base64.php | 6 +- lib/Zend/XmlRpc/Value/Boolean.php | 6 +- lib/Zend/XmlRpc/Value/Collection.php | 12 +- lib/Zend/XmlRpc/Value/DateTime.php | 24 +- lib/Zend/XmlRpc/Value/Double.php | 6 +- lib/Zend/XmlRpc/Value/Exception.php | 6 +- lib/Zend/XmlRpc/Value/Integer.php | 11 +- lib/Zend/XmlRpc/Value/Nil.php | 25 +- lib/Zend/XmlRpc/Value/Scalar.php | 6 +- lib/Zend/XmlRpc/Value/String.php | 22 +- lib/Zend/XmlRpc/Value/Struct.php | 8 +- skin/adminhtml/default/default/boxes.css | 22 +- .../default/images/bg_notifications.gif | Bin 0 -> 1962 bytes .../default/images/bg_severity-critical.gif | Bin 331 -> 0 bytes .../default/images/bg_severity-major.gif | Bin 331 -> 0 bytes .../default/images/bg_severity-minor.gif | Bin 331 -> 0 bytes .../default/images/bg_severity-notice.gif | Bin 331 -> 0 bytes .../widget/catalog__category_widget_link.gif | Bin 0 -> 1390 bytes .../widget/catalog__product_widget_link.gif | Bin 0 -> 1417 bytes .../widget/catalog__product_widget_new.gif | Bin 0 -> 1445 bytes .../images/widget/cms__widget_page_link.gif | Bin 0 -> 1337 bytes .../default/default/images/widget/default.gif | Bin 0 -> 1406 bytes .../reports__product_widget_compared.gif | Bin 0 -> 1572 bytes .../widget/reports__product_widget_viewed.gif | Bin 0 -> 1528 bytes .../default/images/widget_placeholder.gif | Bin 0 -> 965 bytes skin/frontend/default/blank/css/styles-ie.css | 6 +- skin/frontend/default/blank/css/styles.css | 6 +- .../default/blank/images/bkg_button.gif | Bin 1381 -> 1610 bytes skin/frontend/default/blank/js/opcheckout.js | 4 +- skin/frontend/default/blue/js/opcheckout.js | 4 +- .../frontend/default/default/js/opcheckout.js | 4 +- skin/frontend/default/default/js/paypal.js | 2 +- skin/frontend/default/iphone/js/opcheckout.js | 2 +- skin/frontend/default/modern/css/boxes.css | 10 + skin/frontend/default/modern/js/opcheckout.js | 2 +- 3676 files changed, 226236 insertions(+), 44779 deletions(-) create mode 100644 app/code/core/Mage/AdminNotification/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Adminhtml/Block/Catalog/Category/Widget/Chooser.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Catalog/Product/Widget/Chooser.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Cms/Block/Widget/Chooser.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Content.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Cms/Page/Widget/Chooser.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Cms/Widget.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Cms/Widget/Chooser.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Cms/Widget/Form.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Cms/Widget/Options.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Files.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Newfolder.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Uploader.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Tree.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Notification/Baseurl.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Promo/Widget/Chooser/Daterange.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Tag/Assigned/Grid.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Tag/Edit/Accordion.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Tag/Edit/Assigned.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Tag/Store/Switcher.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Theme.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Theme.php create mode 100644 app/code/core/Mage/Adminhtml/Block/Widget/Grid/Serializer.php create mode 100644 app/code/core/Mage/Adminhtml/Model/System/Config/Source/Cms/Wysiwyg/Enabled.php create mode 100644 app/code/core/Mage/Adminhtml/controllers/Catalog/Category/WidgetController.php create mode 100644 app/code/core/Mage/Adminhtml/controllers/Catalog/Product/WidgetController.php create mode 100644 app/code/core/Mage/Adminhtml/controllers/Cms/Block/WidgetController.php create mode 100644 app/code/core/Mage/Adminhtml/controllers/Cms/Page/WidgetController.php create mode 100644 app/code/core/Mage/Adminhtml/controllers/Cms/WidgetController.php create mode 100644 app/code/core/Mage/Adminhtml/controllers/Cms/Wysiwyg/ImagesController.php create mode 100644 app/code/core/Mage/Adminhtml/controllers/Cms/WysiwygController.php create mode 100644 app/code/core/Mage/Adminhtml/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Api/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Backup/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Bundle/Model/Mysql4/Indexer/Price.php create mode 100644 app/code/core/Mage/Bundle/Model/Mysql4/Indexer/Stock.php create mode 100644 app/code/core/Mage/Bundle/sql/bundle_setup/mysql4-upgrade-0.1.8-0.1.9.php create mode 100644 app/code/core/Mage/Bundle/sql/bundle_setup/mysql4-upgrade-0.1.9-0.1.10.php create mode 100644 app/code/core/Mage/Catalog/Block/Category/Widget/Link.php create mode 100644 app/code/core/Mage/Catalog/Block/Product/Widget/Link.php create mode 100644 app/code/core/Mage/Catalog/Block/Product/Widget/New.php create mode 100644 app/code/core/Mage/Catalog/Block/Widget/Link.php create mode 100644 app/code/core/Mage/Catalog/Model/Category/Indexer/Flat.php create mode 100644 app/code/core/Mage/Catalog/Model/Category/Indexer/Product.php create mode 100644 app/code/core/Mage/Catalog/Model/Indexer/Url.php create mode 100644 app/code/core/Mage/Catalog/Model/Product/Action.php create mode 100644 app/code/core/Mage/Catalog/Model/Product/Indexer/Eav.php create mode 100644 app/code/core/Mage/Catalog/Model/Product/Indexer/Flat.php create mode 100644 app/code/core/Mage/Catalog/Model/Product/Indexer/Price.php create mode 100644 app/code/core/Mage/Catalog/Model/Product/Indexer/Status.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Attribute.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Attribute/Collection.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Indexer/Product.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Layer/Filter/Attribute.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Layer/Filter/Price.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Action.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Attribute/Collection.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Abstract.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Eav.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Configurable.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Default.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Grouped.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Interface.php create mode 100644 app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Relation.php create mode 100644 app/code/core/Mage/Catalog/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Catalog/etc/widget.xml create mode 100644 app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-install-1.4.0.0.0.php create mode 100644 app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.72-0.7.73.php create mode 100644 app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.73-1.4.0.0.0.php create mode 100644 app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.0-1.4.0.0.1.php create mode 100644 app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.1-1.4.0.0.2.php create mode 100644 app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.2-1.4.0.0.3.php create mode 100644 app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.3-1.4.0.0.4.php create mode 100644 app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.4-1.4.0.0.5.php create mode 100644 app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.5-1.4.0.0.6.php create mode 100644 app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.6-1.4.0.0.7.php create mode 100644 app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.7-1.4.0.0.8.php create mode 100644 app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.8-1.4.0.0.9.php create mode 100644 app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.9-1.4.0.0.10.php create mode 100644 app/code/core/Mage/CatalogInventory/Model/Indexer/Stock.php create mode 100644 app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock.php create mode 100644 app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Configurable.php create mode 100644 app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Default.php create mode 100644 app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Grouped.php create mode 100644 app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Interface.php create mode 100644 app/code/core/Mage/CatalogInventory/etc/adminhtml.xml create mode 100644 app/code/core/Mage/CatalogRule/etc/adminhtml.xml create mode 100644 app/code/core/Mage/CatalogSearch/Model/Indexer/Fulltext.php create mode 100644 app/code/core/Mage/CatalogSearch/Model/System/Config/Backend/Sitemap.php create mode 100644 app/code/core/Mage/CatalogSearch/etc/adminhtml.xml create mode 100644 app/code/core/Mage/CatalogSearch/sql/catalogsearch_setup/mysql4-upgrade-0.7.6-0.7.7.php create mode 100644 app/code/core/Mage/Checkout/Model/Mysql4/Setup.php create mode 100644 app/code/core/Mage/Checkout/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Checkout/sql/checkout_setup/mysql4-upgrade-0.9.3-0.9.4.php create mode 100644 app/code/core/Mage/Cms/Block/Widget/Block.php create mode 100644 app/code/core/Mage/Cms/Block/Widget/Interface.php create mode 100644 app/code/core/Mage/Cms/Block/Widget/Page/Link.php create mode 100644 app/code/core/Mage/Cms/Helper/Wysiwyg/Images.php create mode 100644 app/code/core/Mage/Cms/Model/Config.php create mode 100644 app/code/core/Mage/Cms/Model/Mysql4/Widget.php create mode 100644 app/code/core/Mage/Cms/Model/Template/Filter.php create mode 100644 app/code/core/Mage/Cms/Model/Widget.php create mode 100644 app/code/core/Mage/Cms/Model/Wysiwyg/Config.php create mode 100644 app/code/core/Mage/Cms/Model/Wysiwyg/Images/Storage.php create mode 100644 app/code/core/Mage/Cms/Model/Wysiwyg/Images/Storage/Collection.php create mode 100644 app/code/core/Mage/Cms/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Cms/etc/widget.xml create mode 100644 app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.10-0.7.11.php create mode 100644 app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.8-0.7.9.php create mode 100644 app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.9-0.7.10.php create mode 100644 app/code/core/Mage/Compiler/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Contacts/Model/System/Config/Backend/Links.php create mode 100644 app/code/core/Mage/Contacts/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Core/Block/Html/Link.php delete mode 100644 app/code/core/Mage/Core/Model/Locale/Currency.php create mode 100644 app/code/core/Mage/Customer/Block/Widget/Gender.php create mode 100644 app/code/core/Mage/Customer/Model/Attribute.php create mode 100644 app/code/core/Mage/Customer/Model/Entity/Address/Attribute/Collection.php create mode 100644 app/code/core/Mage/Customer/Model/Entity/Attribute.php create mode 100644 app/code/core/Mage/Customer/Model/Entity/Attribute/Collection.php create mode 100644 app/code/core/Mage/Customer/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Customer/sql/customer_setup/mysql4-install-1.4.0.0.0.php create mode 100644 app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-0.8.11-0.8.12.php create mode 100644 app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-0.8.12-1.4.0.0.0.php create mode 100644 app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-1.4.0.0.0-1.4.0.0.1.php create mode 100644 app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-1.4.0.0.1-1.4.0.0.2.php create mode 100644 app/code/core/Mage/Downloadable/Model/Mysql4/Indexer/Price.php create mode 100644 app/code/core/Mage/Downloadable/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Downloadable/sql/downloadable_setup/mysql4-upgrade-0.1.15-0.1.16.php create mode 100644 app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Js.php create mode 100644 app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Main/Abstract.php create mode 100644 app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Options/Abstract.php create mode 100644 app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Grid/Abstract.php create mode 100644 app/code/core/Mage/Eav/Model/Adminhtml/System/Config/Source/Inputtype.php create mode 100644 app/code/core/Mage/Eav/Model/Form/Element.php create mode 100644 app/code/core/Mage/Eav/Model/Form/Fieldset.php create mode 100644 app/code/core/Mage/Eav/Model/Form/Type.php create mode 100644 app/code/core/Mage/Eav/Model/Mysql4/Form/Element.php create mode 100644 app/code/core/Mage/Eav/Model/Mysql4/Form/Element/Collection.php create mode 100644 app/code/core/Mage/Eav/Model/Mysql4/Form/Fieldset.php create mode 100644 app/code/core/Mage/Eav/Model/Mysql4/Form/Fieldset/Collection.php create mode 100644 app/code/core/Mage/Eav/Model/Mysql4/Form/Type.php create mode 100644 app/code/core/Mage/Eav/Model/Mysql4/Form/Type/Collection.php create mode 100644 app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.13-0.7.14.php create mode 100644 app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.14-0.7.15.php create mode 100644 app/code/core/Mage/GoogleAnalytics/etc/adminhtml.xml create mode 100644 app/code/core/Mage/GoogleBase/etc/adminhtml.xml create mode 100644 app/code/core/Mage/GoogleCheckout/etc/adminhtml.xml create mode 100644 app/code/core/Mage/GoogleOptimizer/Block/Adminhtml/Cms/Page/Edit/Enable.php create mode 100644 app/code/core/Mage/Index/Block/Adminhtml/Notifications.php create mode 100644 app/code/core/Mage/Index/Block/Adminhtml/Process.php create mode 100644 app/code/core/Mage/Index/Block/Adminhtml/Process/Edit.php create mode 100644 app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Form.php create mode 100644 app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Tab/Main.php create mode 100644 app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Tabs.php create mode 100644 app/code/core/Mage/Index/Block/Adminhtml/Process/Grid.php create mode 100644 app/code/core/Mage/Index/Helper/Data.php create mode 100644 app/code/core/Mage/Index/Model/Event.php create mode 100644 app/code/core/Mage/Index/Model/Indexer.php create mode 100644 app/code/core/Mage/Index/Model/Indexer/Abstract.php create mode 100644 app/code/core/Mage/Index/Model/Mysql4/Abstract.php create mode 100644 app/code/core/Mage/Index/Model/Mysql4/Event.php create mode 100644 app/code/core/Mage/Index/Model/Mysql4/Event/Collection.php create mode 100644 app/code/core/Mage/Index/Model/Mysql4/Process.php create mode 100644 app/code/core/Mage/Index/Model/Mysql4/Process/Collection.php create mode 100644 app/code/core/Mage/Index/Model/Mysql4/Setup.php create mode 100644 app/code/core/Mage/Index/Model/Observer.php create mode 100644 app/code/core/Mage/Index/Model/Process.php create mode 100644 app/code/core/Mage/Index/controllers/Adminhtml/ProcessController.php create mode 100644 app/code/core/Mage/Index/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Index/etc/config.xml create mode 100644 app/code/core/Mage/Index/sql/index_setup/mysql4-install-1.4.0.0.php create mode 100644 app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.4.0.0-1.4.0.1.php create mode 100644 app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.4.0.1-1.4.0.2.php create mode 100644 app/code/core/Mage/Newsletter/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Newsletter/sql/newsletter_setup/mysql4-upgrade-0.8.0-0.8.1.php create mode 100644 app/code/core/Mage/Ogone/Block/Form.php create mode 100644 app/code/core/Mage/Ogone/Block/Info.php create mode 100644 app/code/core/Mage/Ogone/Block/Paypage.php create mode 100644 app/code/core/Mage/Ogone/Block/Placeform.php create mode 100644 app/code/core/Mage/Ogone/Helper/Data.php create mode 100644 app/code/core/Mage/Ogone/Model/Api.php create mode 100644 app/code/core/Mage/Ogone/Model/Api/Debug.php create mode 100644 app/code/core/Mage/Ogone/Model/Config.php create mode 100644 app/code/core/Mage/Ogone/Model/Mysql4/Api/Debug.php create mode 100644 app/code/core/Mage/Ogone/Model/Source/PaymentAction.php create mode 100644 app/code/core/Mage/Ogone/Model/Source/Pmlist.php create mode 100644 app/code/core/Mage/Ogone/Model/Source/Template.php create mode 100644 app/code/core/Mage/Ogone/controllers/ApiController.php create mode 100644 app/code/core/Mage/Ogone/etc/config.xml create mode 100644 app/code/core/Mage/Ogone/etc/system.xml create mode 100644 app/code/core/Mage/Ogone/sql/ogone_setup/mysql4-install-0.0.1.php create mode 100644 app/code/core/Mage/Oscommerce/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Paygate/sql/paygate_setup/mysql4-upgrade-0.7.0-0.7.1.php create mode 100644 app/code/core/Mage/Payment/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Paypal/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Rating/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Reports/Block/Product/Widget/Compared.php create mode 100644 app/code/core/Mage/Reports/Block/Product/Widget/Viewed.php create mode 100644 app/code/core/Mage/Reports/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Reports/etc/widget.xml create mode 100644 app/code/core/Mage/Reports/sql/reports_setup/mysql4-upgrade-0.7.8-0.7.9.php create mode 100644 app/code/core/Mage/Review/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Rss/Model/Observer.php create mode 100644 app/code/core/Mage/Rss/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Sales/Model/Order/Creditmemo/Total/Cost.php create mode 100644 app/code/core/Mage/Sales/Model/Order/Invoice/Total/Cost.php create mode 100644 app/code/core/Mage/Sales/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.40-0.9.41.php create mode 100644 app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.41-0.9.42.php create mode 100644 app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.42-0.9.43.php create mode 100644 app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.43-0.9.44.php create mode 100644 app/code/core/Mage/SalesRule/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Shipping/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Sitemap/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Strikeiron/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Tag/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Tag/sql/tag_setup/mysql4-upgrade-0.7.2-0.7.3.php create mode 100644 app/code/core/Mage/Tax/etc/adminhtml.xml create mode 100644 app/code/core/Mage/Tax/sql/tax_setup/mysql4-upgrade-0.7.9-0.7.10.php create mode 100644 app/code/core/Mage/Usa/sql/usa_setup/mysql4-upgrade-0.7.0-0.7.1.php create mode 100644 app/code/core/Mage/Wishlist/etc/adminhtml.xml create mode 100644 app/design/adminhtml/default/default/layout/cms.xml create mode 100644 app/design/adminhtml/default/default/layout/index.xml create mode 100644 app/design/adminhtml/default/default/layout/promo.xml create mode 100644 app/design/adminhtml/default/default/layout/tag.xml create mode 100644 app/design/adminhtml/default/default/template/catalog/category/widget/tree.phtml create mode 100644 app/design/adminhtml/default/default/template/cms/page/edit/form/renderer/content.phtml rename app/design/adminhtml/default/default/template/{tag/edit.phtml => cms/wysiwyg/content.phtml} (59%) create mode 100644 app/design/adminhtml/default/default/template/cms/wysiwyg/content/files.phtml create mode 100644 app/design/adminhtml/default/default/template/cms/wysiwyg/content/newfolder.phtml create mode 100644 app/design/adminhtml/default/default/template/cms/wysiwyg/content/uploader.phtml create mode 100644 app/design/adminhtml/default/default/template/cms/wysiwyg/js.phtml create mode 100644 app/design/adminhtml/default/default/template/cms/wysiwyg/tree.phtml create mode 100644 app/design/adminhtml/default/default/template/directory/js/optional_zip_countries.phtml create mode 100644 app/design/adminhtml/default/default/template/eav/attribute/edit/js.phtml create mode 100644 app/design/adminhtml/default/default/template/index/notifications.phtml create mode 100644 app/design/adminhtml/default/default/template/notification/baseurl.phtml create mode 100644 app/design/adminhtml/default/default/template/ogone/info.phtml create mode 100644 app/design/adminhtml/default/default/template/tag/edit/container.phtml create mode 100644 app/design/adminhtml/default/default/template/tag/edit/serializer.phtml create mode 100644 app/design/adminhtml/default/default/template/widget/grid/serializer.phtml create mode 100644 app/design/frontend/default/blank/layout/ogone.xml create mode 100644 app/design/frontend/default/blank/template/customer/widget/gender.phtml create mode 100644 app/design/frontend/default/blank/template/ogone/form.phtml create mode 100644 app/design/frontend/default/blank/template/ogone/info.phtml create mode 100644 app/design/frontend/default/blank/template/ogone/paypage.phtml create mode 100644 app/design/frontend/default/blank/template/ogone/placeform.phtml create mode 100644 app/design/frontend/default/blank/template/reports/product/widget/compared.phtml create mode 100644 app/design/frontend/default/blank/template/reports/product/widget/viewed.phtml create mode 100644 app/design/frontend/default/default/layout/ogone.xml create mode 100644 app/design/frontend/default/default/template/customer/widget/gender.phtml create mode 100644 app/design/frontend/default/default/template/directory/js/optional_zip_countries.phtml create mode 100644 app/design/frontend/default/default/template/ogone/form.phtml create mode 100644 app/design/frontend/default/default/template/ogone/info.phtml create mode 100644 app/design/frontend/default/default/template/ogone/paypage.phtml create mode 100644 app/design/frontend/default/default/template/ogone/placeform.phtml create mode 100644 app/design/frontend/default/default/template/reports/product/widget/compared.phtml create mode 100644 app/design/frontend/default/default/template/reports/product/widget/viewed.phtml create mode 100644 app/design/frontend/default/iphone/layout/ogone.xml create mode 100644 app/design/frontend/default/iphone/template/customer/widget/gender.phtml create mode 100644 app/design/frontend/default/iphone/template/ogone/form.phtml create mode 100644 app/design/frontend/default/iphone/template/ogone/info.phtml create mode 100644 app/design/frontend/default/iphone/template/ogone/paypage.phtml create mode 100644 app/design/frontend/default/iphone/template/ogone/placeform.phtml create mode 100644 app/design/frontend/default/modern/layout/ogone.xml create mode 100644 app/design/frontend/default/modern/template/customer/widget/gender.phtml create mode 100644 app/design/frontend/default/modern/template/ogone/form.phtml create mode 100644 app/design/frontend/default/modern/template/ogone/info.phtml create mode 100644 app/design/frontend/default/modern/template/ogone/paypage.phtml create mode 100644 app/design/frontend/default/modern/template/ogone/placeform.phtml create mode 100644 app/design/frontend/default/modern/template/reports/product/widget/compared.phtml create mode 100644 app/design/frontend/default/modern/template/reports/product/widget/viewed.phtml create mode 100644 app/etc/modules/Mage_Ogone.xml create mode 100644 js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js create mode 100644 js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin_src.js create mode 100644 js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/img/icon.gif create mode 100644 js/mage/adminhtml/wysiwyg/tiny_mce/setup.js create mode 100644 js/mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/content.css create mode 100644 js/mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/dialog.css create mode 100644 js/mage/adminhtml/wysiwyg/widget.js create mode 100644 js/tiny_mce/blank.htm create mode 100644 js/tiny_mce/classes/AddOnManager.js create mode 100644 js/tiny_mce/classes/CommandManager.js create mode 100644 js/tiny_mce/classes/ControlManager.js create mode 100644 js/tiny_mce/classes/Developer.js create mode 100644 js/tiny_mce/classes/Editor.js create mode 100644 js/tiny_mce/classes/EditorCommands.js create mode 100644 js/tiny_mce/classes/EditorManager.js create mode 100644 js/tiny_mce/classes/ForceBlocks.js create mode 100644 js/tiny_mce/classes/Popup.js create mode 100644 js/tiny_mce/classes/UndoManager.js create mode 100644 js/tiny_mce/classes/WindowManager.js create mode 100644 js/tiny_mce/classes/adapter/jquery/adapter.js create mode 100644 js/tiny_mce/classes/adapter/jquery/jquery.tinymce.js create mode 100644 js/tiny_mce/classes/adapter/prototype/adapter.js create mode 100644 js/tiny_mce/classes/commands/BlockQuote.js create mode 100644 js/tiny_mce/classes/commands/CutCopyPaste.js create mode 100644 js/tiny_mce/classes/commands/InsertHorizontalRule.js create mode 100644 js/tiny_mce/classes/commands/RemoveFormat.js create mode 100644 js/tiny_mce/classes/commands/UndoRedo.js create mode 100644 js/tiny_mce/classes/dom/DOMUtils.js create mode 100644 js/tiny_mce/classes/dom/Element.js create mode 100644 js/tiny_mce/classes/dom/EventUtils.js create mode 100644 js/tiny_mce/classes/dom/Range.js create mode 100644 js/tiny_mce/classes/dom/ScriptLoader.js create mode 100644 js/tiny_mce/classes/dom/Selection.js create mode 100644 js/tiny_mce/classes/dom/Serializer.js create mode 100644 js/tiny_mce/classes/dom/Sizzle.js create mode 100644 js/tiny_mce/classes/dom/StringWriter.js create mode 100644 js/tiny_mce/classes/dom/TridentSelection.js create mode 100644 js/tiny_mce/classes/dom/XMLWriter.js create mode 100644 js/tiny_mce/classes/firebug/firebug-lite.js create mode 100644 js/tiny_mce/classes/tinymce.js create mode 100644 js/tiny_mce/classes/ui/Button.js create mode 100644 js/tiny_mce/classes/ui/ColorSplitButton.js create mode 100644 js/tiny_mce/classes/ui/Container.js create mode 100644 js/tiny_mce/classes/ui/Control.js create mode 100644 js/tiny_mce/classes/ui/DropMenu.js create mode 100644 js/tiny_mce/classes/ui/ListBox.js create mode 100644 js/tiny_mce/classes/ui/Menu.js create mode 100644 js/tiny_mce/classes/ui/MenuButton.js create mode 100644 js/tiny_mce/classes/ui/MenuItem.js create mode 100644 js/tiny_mce/classes/ui/NativeListBox.js create mode 100644 js/tiny_mce/classes/ui/Separator.js create mode 100644 js/tiny_mce/classes/ui/SplitButton.js create mode 100644 js/tiny_mce/classes/ui/Toolbar.js create mode 100644 js/tiny_mce/classes/util/Cookie.js create mode 100644 js/tiny_mce/classes/util/Dispatcher.js create mode 100644 js/tiny_mce/classes/util/JSON.js create mode 100644 js/tiny_mce/classes/util/JSONP.js create mode 100644 js/tiny_mce/classes/util/JSONRequest.js create mode 100644 js/tiny_mce/classes/util/URI.js create mode 100644 js/tiny_mce/classes/util/XHR.js create mode 100644 js/tiny_mce/classes/xml/Parser.js create mode 100644 js/tiny_mce/jquery.tinymce.js create mode 100644 js/tiny_mce/langs/en.js create mode 100644 js/tiny_mce/license.txt create mode 100644 js/tiny_mce/plugins/advhr/css/advhr.css create mode 100644 js/tiny_mce/plugins/advhr/editor_plugin.js create mode 100644 js/tiny_mce/plugins/advhr/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/advhr/js/rule.js create mode 100644 js/tiny_mce/plugins/advhr/langs/en_dlg.js create mode 100644 js/tiny_mce/plugins/advhr/rule.htm create mode 100644 js/tiny_mce/plugins/advimage/css/advimage.css create mode 100644 js/tiny_mce/plugins/advimage/editor_plugin.js create mode 100644 js/tiny_mce/plugins/advimage/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/advimage/image.htm create mode 100644 js/tiny_mce/plugins/advimage/img/sample.gif create mode 100644 js/tiny_mce/plugins/advimage/js/image.js create mode 100644 js/tiny_mce/plugins/advimage/langs/en_dlg.js create mode 100644 js/tiny_mce/plugins/advlink/css/advlink.css create mode 100644 js/tiny_mce/plugins/advlink/editor_plugin.js create mode 100644 js/tiny_mce/plugins/advlink/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/advlink/js/advlink.js create mode 100644 js/tiny_mce/plugins/advlink/langs/en_dlg.js create mode 100644 js/tiny_mce/plugins/advlink/link.htm create mode 100644 js/tiny_mce/plugins/autoresize/editor_plugin.js create mode 100644 js/tiny_mce/plugins/autoresize/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/autosave/editor_plugin.js create mode 100644 js/tiny_mce/plugins/autosave/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/bbcode/editor_plugin.js create mode 100644 js/tiny_mce/plugins/bbcode/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/compat2x/editor_plugin.js create mode 100644 js/tiny_mce/plugins/compat2x/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/contextmenu/editor_plugin.js create mode 100644 js/tiny_mce/plugins/contextmenu/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/directionality/editor_plugin.js create mode 100644 js/tiny_mce/plugins/directionality/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/emotions/editor_plugin.js create mode 100644 js/tiny_mce/plugins/emotions/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/emotions/emotions.htm create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-cool.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-cry.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-embarassed.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-foot-in-mouth.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-frown.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-innocent.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-kiss.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-laughing.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-money-mouth.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-sealed.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-smile.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-surprised.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-tongue-out.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-undecided.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-wink.gif create mode 100644 js/tiny_mce/plugins/emotions/img/smiley-yell.gif create mode 100644 js/tiny_mce/plugins/emotions/js/emotions.js create mode 100644 js/tiny_mce/plugins/emotions/langs/en_dlg.js create mode 100644 js/tiny_mce/plugins/example/dialog.htm create mode 100644 js/tiny_mce/plugins/example/editor_plugin.js create mode 100644 js/tiny_mce/plugins/example/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/example/img/example.gif create mode 100644 js/tiny_mce/plugins/example/js/dialog.js create mode 100644 js/tiny_mce/plugins/example/langs/en.js create mode 100644 js/tiny_mce/plugins/example/langs/en_dlg.js create mode 100644 js/tiny_mce/plugins/fullpage/css/fullpage.css create mode 100644 js/tiny_mce/plugins/fullpage/editor_plugin.js create mode 100644 js/tiny_mce/plugins/fullpage/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/fullpage/fullpage.htm create mode 100644 js/tiny_mce/plugins/fullpage/js/fullpage.js create mode 100644 js/tiny_mce/plugins/fullpage/langs/en_dlg.js create mode 100644 js/tiny_mce/plugins/fullscreen/editor_plugin.js create mode 100644 js/tiny_mce/plugins/fullscreen/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/fullscreen/fullscreen.htm create mode 100644 js/tiny_mce/plugins/iespell/editor_plugin.js create mode 100644 js/tiny_mce/plugins/iespell/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/inlinepopups/editor_plugin.js create mode 100644 js/tiny_mce/plugins/inlinepopups/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/alert.gif create mode 100644 js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/button.gif create mode 100644 js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/buttons.gif create mode 100644 js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/confirm.gif create mode 100644 js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/corners.gif create mode 100644 js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/horizontal.gif create mode 100644 js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/vertical.gif create mode 100644 js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/window.css create mode 100644 js/tiny_mce/plugins/inlinepopups/template.htm create mode 100644 js/tiny_mce/plugins/insertdatetime/editor_plugin.js create mode 100644 js/tiny_mce/plugins/insertdatetime/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/layer/editor_plugin.js create mode 100644 js/tiny_mce/plugins/layer/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/media/css/content.css create mode 100644 js/tiny_mce/plugins/media/css/media.css create mode 100644 js/tiny_mce/plugins/media/editor_plugin.js create mode 100644 js/tiny_mce/plugins/media/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/media/img/flash.gif create mode 100644 js/tiny_mce/plugins/media/img/flv_player.swf create mode 100644 js/tiny_mce/plugins/media/img/quicktime.gif create mode 100644 js/tiny_mce/plugins/media/img/realmedia.gif create mode 100644 js/tiny_mce/plugins/media/img/shockwave.gif create mode 100644 js/tiny_mce/plugins/media/img/trans.gif create mode 100644 js/tiny_mce/plugins/media/img/windowsmedia.gif create mode 100644 js/tiny_mce/plugins/media/js/embed.js create mode 100644 js/tiny_mce/plugins/media/js/media.js create mode 100644 js/tiny_mce/plugins/media/langs/en_dlg.js create mode 100644 js/tiny_mce/plugins/media/media.htm create mode 100644 js/tiny_mce/plugins/nonbreaking/editor_plugin.js create mode 100644 js/tiny_mce/plugins/nonbreaking/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/noneditable/editor_plugin.js create mode 100644 js/tiny_mce/plugins/noneditable/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/pagebreak/css/content.css create mode 100644 js/tiny_mce/plugins/pagebreak/editor_plugin.js create mode 100644 js/tiny_mce/plugins/pagebreak/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/pagebreak/img/pagebreak.gif create mode 100644 js/tiny_mce/plugins/pagebreak/img/trans.gif create mode 100644 js/tiny_mce/plugins/paste/editor_plugin.js create mode 100644 js/tiny_mce/plugins/paste/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/paste/js/pastetext.js create mode 100644 js/tiny_mce/plugins/paste/js/pasteword.js create mode 100644 js/tiny_mce/plugins/paste/langs/en_dlg.js create mode 100644 js/tiny_mce/plugins/paste/pastetext.htm create mode 100644 js/tiny_mce/plugins/paste/pasteword.htm create mode 100644 js/tiny_mce/plugins/preview/editor_plugin.js create mode 100644 js/tiny_mce/plugins/preview/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/preview/example.html create mode 100644 js/tiny_mce/plugins/preview/jscripts/embed.js create mode 100644 js/tiny_mce/plugins/preview/preview.html create mode 100644 js/tiny_mce/plugins/print/editor_plugin.js create mode 100644 js/tiny_mce/plugins/print/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/safari/blank.htm create mode 100644 js/tiny_mce/plugins/safari/editor_plugin.js create mode 100644 js/tiny_mce/plugins/safari/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/save/editor_plugin.js create mode 100644 js/tiny_mce/plugins/save/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/searchreplace/css/searchreplace.css create mode 100644 js/tiny_mce/plugins/searchreplace/editor_plugin.js create mode 100644 js/tiny_mce/plugins/searchreplace/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/searchreplace/js/searchreplace.js create mode 100644 js/tiny_mce/plugins/searchreplace/langs/en_dlg.js create mode 100644 js/tiny_mce/plugins/searchreplace/searchreplace.htm create mode 100644 js/tiny_mce/plugins/spellchecker/css/content.css create mode 100644 js/tiny_mce/plugins/spellchecker/editor_plugin.js create mode 100644 js/tiny_mce/plugins/spellchecker/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/spellchecker/img/wline.gif create mode 100644 js/tiny_mce/plugins/style/css/props.css create mode 100644 js/tiny_mce/plugins/style/editor_plugin.js create mode 100644 js/tiny_mce/plugins/style/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/style/js/props.js create mode 100644 js/tiny_mce/plugins/style/langs/en_dlg.js create mode 100644 js/tiny_mce/plugins/style/props.htm create mode 100644 js/tiny_mce/plugins/tabfocus/editor_plugin.js create mode 100644 js/tiny_mce/plugins/tabfocus/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/table/cell.htm create mode 100644 js/tiny_mce/plugins/table/css/cell.css create mode 100644 js/tiny_mce/plugins/table/css/row.css create mode 100644 js/tiny_mce/plugins/table/css/table.css create mode 100644 js/tiny_mce/plugins/table/editor_plugin.js create mode 100644 js/tiny_mce/plugins/table/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/table/js/cell.js create mode 100644 js/tiny_mce/plugins/table/js/merge_cells.js create mode 100644 js/tiny_mce/plugins/table/js/row.js create mode 100644 js/tiny_mce/plugins/table/js/table.js create mode 100644 js/tiny_mce/plugins/table/langs/en_dlg.js create mode 100644 js/tiny_mce/plugins/table/merge_cells.htm create mode 100644 js/tiny_mce/plugins/table/row.htm create mode 100644 js/tiny_mce/plugins/table/table.htm create mode 100644 js/tiny_mce/plugins/template/blank.htm create mode 100644 js/tiny_mce/plugins/template/css/template.css create mode 100644 js/tiny_mce/plugins/template/editor_plugin.js create mode 100644 js/tiny_mce/plugins/template/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/template/js/template.js create mode 100644 js/tiny_mce/plugins/template/langs/en_dlg.js create mode 100644 js/tiny_mce/plugins/template/template.htm create mode 100644 js/tiny_mce/plugins/visualchars/editor_plugin.js create mode 100644 js/tiny_mce/plugins/visualchars/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/xhtmlxtras/abbr.htm create mode 100644 js/tiny_mce/plugins/xhtmlxtras/acronym.htm create mode 100644 js/tiny_mce/plugins/xhtmlxtras/attributes.htm create mode 100644 js/tiny_mce/plugins/xhtmlxtras/cite.htm create mode 100644 js/tiny_mce/plugins/xhtmlxtras/css/attributes.css create mode 100644 js/tiny_mce/plugins/xhtmlxtras/css/popup.css create mode 100644 js/tiny_mce/plugins/xhtmlxtras/del.htm create mode 100644 js/tiny_mce/plugins/xhtmlxtras/editor_plugin.js create mode 100644 js/tiny_mce/plugins/xhtmlxtras/editor_plugin_src.js create mode 100644 js/tiny_mce/plugins/xhtmlxtras/ins.htm create mode 100644 js/tiny_mce/plugins/xhtmlxtras/js/abbr.js create mode 100644 js/tiny_mce/plugins/xhtmlxtras/js/acronym.js create mode 100644 js/tiny_mce/plugins/xhtmlxtras/js/attributes.js create mode 100644 js/tiny_mce/plugins/xhtmlxtras/js/cite.js create mode 100644 js/tiny_mce/plugins/xhtmlxtras/js/del.js create mode 100644 js/tiny_mce/plugins/xhtmlxtras/js/element_common.js create mode 100644 js/tiny_mce/plugins/xhtmlxtras/js/ins.js create mode 100644 js/tiny_mce/plugins/xhtmlxtras/langs/en_dlg.js create mode 100644 js/tiny_mce/themes/advanced/about.htm create mode 100644 js/tiny_mce/themes/advanced/anchor.htm create mode 100644 js/tiny_mce/themes/advanced/charmap.htm create mode 100644 js/tiny_mce/themes/advanced/color_picker.htm create mode 100644 js/tiny_mce/themes/advanced/editor_template.js create mode 100644 js/tiny_mce/themes/advanced/editor_template_src.js create mode 100644 js/tiny_mce/themes/advanced/image.htm create mode 100644 js/tiny_mce/themes/advanced/img/colorpicker.jpg create mode 100644 js/tiny_mce/themes/advanced/img/icons.gif create mode 100644 js/tiny_mce/themes/advanced/js/about.js create mode 100644 js/tiny_mce/themes/advanced/js/anchor.js create mode 100644 js/tiny_mce/themes/advanced/js/charmap.js create mode 100644 js/tiny_mce/themes/advanced/js/color_picker.js create mode 100644 js/tiny_mce/themes/advanced/js/image.js create mode 100644 js/tiny_mce/themes/advanced/js/link.js create mode 100644 js/tiny_mce/themes/advanced/js/source_editor.js create mode 100644 js/tiny_mce/themes/advanced/langs/en.js create mode 100644 js/tiny_mce/themes/advanced/langs/en_dlg.js create mode 100644 js/tiny_mce/themes/advanced/link.htm create mode 100644 js/tiny_mce/themes/advanced/skins/default/content.css create mode 100644 js/tiny_mce/themes/advanced/skins/default/dialog.css create mode 100644 js/tiny_mce/themes/advanced/skins/default/img/buttons.png create mode 100644 js/tiny_mce/themes/advanced/skins/default/img/items.gif create mode 100644 js/tiny_mce/themes/advanced/skins/default/img/menu_arrow.gif create mode 100644 js/tiny_mce/themes/advanced/skins/default/img/menu_check.gif create mode 100644 js/tiny_mce/themes/advanced/skins/default/img/progress.gif create mode 100644 js/tiny_mce/themes/advanced/skins/default/img/tabs.gif create mode 100644 js/tiny_mce/themes/advanced/skins/default/ui.css create mode 100644 js/tiny_mce/themes/advanced/skins/o2k7/content.css create mode 100644 js/tiny_mce/themes/advanced/skins/o2k7/dialog.css create mode 100644 js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg.png create mode 100644 js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg_black.png create mode 100644 js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg_silver.png create mode 100644 js/tiny_mce/themes/advanced/skins/o2k7/ui.css create mode 100644 js/tiny_mce/themes/advanced/skins/o2k7/ui_black.css create mode 100644 js/tiny_mce/themes/advanced/skins/o2k7/ui_silver.css create mode 100644 js/tiny_mce/themes/advanced/source_editor.htm create mode 100644 js/tiny_mce/themes/simple/editor_template.js create mode 100644 js/tiny_mce/themes/simple/editor_template_src.js create mode 100644 js/tiny_mce/themes/simple/img/icons.gif create mode 100644 js/tiny_mce/themes/simple/langs/en.js create mode 100644 js/tiny_mce/themes/simple/skins/default/content.css create mode 100644 js/tiny_mce/themes/simple/skins/default/ui.css create mode 100644 js/tiny_mce/themes/simple/skins/o2k7/content.css create mode 100644 js/tiny_mce/themes/simple/skins/o2k7/img/button_bg.png create mode 100644 js/tiny_mce/themes/simple/skins/o2k7/ui.css create mode 100644 js/tiny_mce/tiny_mce.js create mode 100644 js/tiny_mce/tiny_mce_dev.js create mode 100644 js/tiny_mce/tiny_mce_jquery.js create mode 100644 js/tiny_mce/tiny_mce_jquery_src.js create mode 100644 js/tiny_mce/tiny_mce_popup.js create mode 100644 js/tiny_mce/tiny_mce_prototype.js create mode 100644 js/tiny_mce/tiny_mce_prototype_src.js create mode 100644 js/tiny_mce/tiny_mce_src.js create mode 100644 js/tiny_mce/utils/editable_selects.js create mode 100644 js/tiny_mce/utils/form_utils.js create mode 100644 js/tiny_mce/utils/mctabs.js create mode 100644 js/tiny_mce/utils/validate.js create mode 100644 lib/Varien/Data/Form/Element/Link.php create mode 100644 lib/Zend/CodeGenerator/Php/Property/DefaultValue.php create mode 100644 lib/Zend/Controller/Plugin/PutHandler.php create mode 100644 lib/Zend/Db/Adapter/Sqlsrv.php create mode 100644 lib/Zend/Db/Adapter/Sqlsrv/Exception.php create mode 100644 lib/Zend/Db/Statement/Sqlsrv.php create mode 100644 lib/Zend/Db/Statement/Sqlsrv/Exception.php create mode 100644 lib/Zend/Db/Table/Definition.php create mode 100644 lib/Zend/Dojo/BuildLayer.php create mode 100644 lib/Zend/Feed/Reader.php create mode 100644 lib/Zend/Feed/Reader/Entry/Atom.php create mode 100644 lib/Zend/Feed/Reader/Entry/Rss.php create mode 100644 lib/Zend/Feed/Reader/EntryAbstract.php create mode 100644 lib/Zend/Feed/Reader/EntryInterface.php create mode 100644 lib/Zend/Feed/Reader/Extension/Atom/Entry.php create mode 100644 lib/Zend/Feed/Reader/Extension/Atom/Feed.php create mode 100644 lib/Zend/Feed/Reader/Extension/Content/Entry.php create mode 100644 lib/Zend/Feed/Reader/Extension/CreativeCommons/Entry.php create mode 100644 lib/Zend/Feed/Reader/Extension/CreativeCommons/Feed.php create mode 100644 lib/Zend/Feed/Reader/Extension/DublinCore/Entry.php create mode 100644 lib/Zend/Feed/Reader/Extension/DublinCore/Feed.php create mode 100644 lib/Zend/Feed/Reader/Extension/EntryAbstract.php create mode 100644 lib/Zend/Feed/Reader/Extension/FeedAbstract.php create mode 100644 lib/Zend/Feed/Reader/Extension/Podcast/Entry.php create mode 100644 lib/Zend/Feed/Reader/Extension/Podcast/Feed.php create mode 100644 lib/Zend/Feed/Reader/Extension/Slash/Entry.php create mode 100644 lib/Zend/Feed/Reader/Extension/Syndication/Feed.php create mode 100644 lib/Zend/Feed/Reader/Extension/Thread/Entry.php create mode 100644 lib/Zend/Feed/Reader/Extension/WellFormedWeb/Entry.php create mode 100644 lib/Zend/Feed/Reader/Feed/Atom.php create mode 100644 lib/Zend/Feed/Reader/Feed/Rss.php create mode 100644 lib/Zend/Feed/Reader/FeedAbstract.php create mode 100644 lib/Zend/Feed/Reader/FeedInterface.php create mode 100644 lib/Zend/Ldap/Attribute.php create mode 100644 lib/Zend/Ldap/Collection.php create mode 100644 lib/Zend/Ldap/Collection/Iterator/Default.php rename lib/Zend/{Pdf/Parser/Stream.php => Ldap/Collection/Iterator/Interface.php} (50%) create mode 100644 lib/Zend/Ldap/Converter.php create mode 100644 lib/Zend/Ldap/Dn.php create mode 100644 lib/Zend/Ldap/Filter.php create mode 100644 lib/Zend/Ldap/Filter/Abstract.php create mode 100644 lib/Zend/Ldap/Filter/And.php create mode 100644 lib/Zend/Ldap/Filter/Exception.php create mode 100644 lib/Zend/Ldap/Filter/Logical.php create mode 100644 lib/Zend/Ldap/Filter/Mask.php create mode 100644 lib/Zend/Ldap/Filter/Not.php create mode 100644 lib/Zend/Ldap/Filter/Or.php create mode 100644 lib/Zend/Ldap/Filter/String.php create mode 100644 lib/Zend/Ldap/Ldif/Encoder.php create mode 100644 lib/Zend/Ldap/Node.php create mode 100644 lib/Zend/Ldap/Node/Abstract.php create mode 100644 lib/Zend/Ldap/Node/ChildrenIterator.php create mode 100644 lib/Zend/Ldap/Node/Collection.php create mode 100644 lib/Zend/Ldap/Node/RootDse.php create mode 100644 lib/Zend/Ldap/Node/RootDse/ActiveDirectory.php create mode 100644 lib/Zend/Ldap/Node/RootDse/OpenLdap.php create mode 100644 lib/Zend/Ldap/Node/RootDse/eDirectory.php create mode 100644 lib/Zend/Ldap/Node/Schema.php create mode 100644 lib/Zend/Ldap/Node/Schema/ActiveDirectory.php create mode 100644 lib/Zend/Ldap/Node/Schema/AttributeType/ActiveDirectory.php create mode 100644 lib/Zend/Ldap/Node/Schema/AttributeType/Interface.php create mode 100644 lib/Zend/Ldap/Node/Schema/AttributeType/OpenLdap.php create mode 100644 lib/Zend/Ldap/Node/Schema/Item.php create mode 100644 lib/Zend/Ldap/Node/Schema/ObjectClass/ActiveDirectory.php create mode 100644 lib/Zend/Ldap/Node/Schema/ObjectClass/Interface.php create mode 100644 lib/Zend/Ldap/Node/Schema/ObjectClass/OpenLdap.php create mode 100644 lib/Zend/Ldap/Node/Schema/OpenLdap.php create mode 100644 lib/Zend/Locale/Data/aa_ER_SAAHO.xml create mode 100644 lib/Zend/Locale/Data/bo.xml create mode 100644 lib/Zend/Locale/Data/bo_CN.xml create mode 100644 lib/Zend/Locale/Data/bo_IN.xml create mode 100644 lib/Zend/Locale/Data/en_Dsrt.xml create mode 100644 lib/Zend/Locale/Data/en_Dsrt_US.xml create mode 100644 lib/Zend/Locale/Data/gsw.xml create mode 100644 lib/Zend/Locale/Data/gsw_CH.xml create mode 100644 lib/Zend/Locale/Data/ku_Arab_IQ.xml create mode 100644 lib/Zend/Locale/Data/ku_Arab_IR.xml create mode 100644 lib/Zend/Locale/Data/ku_Arab_SY.xml create mode 100644 lib/Zend/Locale/Data/ku_IQ.xml create mode 100644 lib/Zend/Locale/Data/ku_IR.xml create mode 100644 lib/Zend/Locale/Data/ku_SY.xml create mode 100644 lib/Zend/Locale/Data/likelySubtags.xml create mode 100644 lib/Zend/Locale/Data/metazoneInfo.xml create mode 100644 lib/Zend/Locale/Data/nds.xml create mode 100644 lib/Zend/Locale/Data/nds_DE.xml create mode 100644 lib/Zend/Locale/Data/numberingSystems.xml create mode 100644 lib/Zend/Locale/Data/oc.xml create mode 100644 lib/Zend/Locale/Data/oc_FR.xml delete mode 100644 lib/Zend/Locale/Data/plurals.xml create mode 100644 lib/Zend/Locale/Data/postalCodeData.xml create mode 100644 lib/Zend/Locale/Data/trv.xml create mode 100644 lib/Zend/Locale/Data/trv_TW.xml create mode 100644 lib/Zend/Log/Writer/Syslog.php create mode 100644 lib/Zend/Paginator/AdapterAggregate.php create mode 100644 lib/Zend/Pdf/Action.php create mode 100644 lib/Zend/Pdf/Action/GoTo.php create mode 100644 lib/Zend/Pdf/Action/GoTo3DView.php create mode 100644 lib/Zend/Pdf/Action/GoToE.php create mode 100644 lib/Zend/Pdf/Action/GoToR.php create mode 100644 lib/Zend/Pdf/Action/Hide.php create mode 100644 lib/Zend/Pdf/Action/ImportData.php create mode 100644 lib/Zend/Pdf/Action/JavaScript.php create mode 100644 lib/Zend/Pdf/Action/Launch.php create mode 100644 lib/Zend/Pdf/Action/Movie.php create mode 100644 lib/Zend/Pdf/Action/Named.php create mode 100644 lib/Zend/Pdf/Action/Rendition.php create mode 100644 lib/Zend/Pdf/Action/ResetForm.php create mode 100644 lib/Zend/Pdf/Action/SetOCGState.php create mode 100644 lib/Zend/Pdf/Action/Sound.php create mode 100644 lib/Zend/Pdf/Action/SubmitForm.php create mode 100644 lib/Zend/Pdf/Action/Thread.php create mode 100644 lib/Zend/Pdf/Action/Trans.php create mode 100644 lib/Zend/Pdf/Action/URI.php create mode 100644 lib/Zend/Pdf/Action/Unknown.php create mode 100644 lib/Zend/Pdf/Annotation.php create mode 100644 lib/Zend/Pdf/Annotation/FileAttachment.php create mode 100644 lib/Zend/Pdf/Annotation/Link.php create mode 100644 lib/Zend/Pdf/Annotation/Text.php create mode 100644 lib/Zend/Pdf/Destination.php create mode 100644 lib/Zend/Pdf/Destination/Explicit.php create mode 100644 lib/Zend/Pdf/Destination/Fit.php create mode 100644 lib/Zend/Pdf/Destination/FitBoundingBox.php create mode 100644 lib/Zend/Pdf/Destination/FitBoundingBoxHorizontally.php create mode 100644 lib/Zend/Pdf/Destination/FitBoundingBoxVertically.php create mode 100644 lib/Zend/Pdf/Destination/FitHorizontally.php create mode 100644 lib/Zend/Pdf/Destination/FitRectangle.php create mode 100644 lib/Zend/Pdf/Destination/FitVertically.php create mode 100644 lib/Zend/Pdf/Destination/Named.php create mode 100644 lib/Zend/Pdf/Destination/Unknown.php create mode 100644 lib/Zend/Pdf/Destination/Zoom.php create mode 100644 lib/Zend/Pdf/NameTree.php create mode 100644 lib/Zend/Pdf/Outline.php create mode 100644 lib/Zend/Pdf/Outline/Created.php create mode 100644 lib/Zend/Pdf/Outline/Loaded.php delete mode 100644 lib/Zend/Pdf/PhpArray.php create mode 100644 lib/Zend/Pdf/RecursivelyIteratableObjectsContainer.php create mode 100644 lib/Zend/Pdf/Target.php create mode 100644 lib/Zend/Queue.php create mode 100644 lib/Zend/Queue/Adapter/Activemq.php create mode 100644 lib/Zend/Queue/Adapter/AdapterAbstract.php create mode 100644 lib/Zend/Queue/Adapter/AdapterInterface.php create mode 100644 lib/Zend/Queue/Adapter/Array.php create mode 100644 lib/Zend/Queue/Adapter/Db.php create mode 100644 lib/Zend/Queue/Adapter/Db/Message.php create mode 100644 lib/Zend/Queue/Adapter/Db/Queue.php create mode 100644 lib/Zend/Queue/Adapter/Db/queue.sql create mode 100644 lib/Zend/Queue/Adapter/Memcacheq.php create mode 100644 lib/Zend/Queue/Adapter/Null.php create mode 100644 lib/Zend/Queue/Adapter/PlatformJobQueue.php create mode 100644 lib/Zend/Queue/Exception.php create mode 100644 lib/Zend/Queue/Message.php create mode 100644 lib/Zend/Queue/Message/Iterator.php create mode 100644 lib/Zend/Queue/Message/PlatformJob.php create mode 100644 lib/Zend/Queue/Stomp/Client.php create mode 100644 lib/Zend/Queue/Stomp/Client/Connection.php create mode 100644 lib/Zend/Queue/Stomp/Client/ConnectionInterface.php create mode 100644 lib/Zend/Queue/Stomp/Frame.php create mode 100644 lib/Zend/Queue/Stomp/FrameInterface.php create mode 100644 lib/Zend/Rest/Controller.php create mode 100644 lib/Zend/Rest/Route.php create mode 100644 lib/Zend/Service/Amazon/Ec2/CloudWatch.php create mode 100644 lib/Zend/Service/Amazon/Ec2/Instance/Reserved.php create mode 100644 lib/Zend/Service/Amazon/Ec2/Instance/Windows.php create mode 100644 lib/Zend/Service/Amazon/Sqs.php create mode 100644 lib/Zend/Service/Amazon/Sqs/Exception.php create mode 100644 lib/Zend/Test/DbAdapter.php create mode 100644 lib/Zend/Test/DbStatement.php create mode 100644 lib/Zend/Test/PHPUnit/DatabaseTestCase.php create mode 100644 lib/Zend/Test/PHPUnit/Db/Connection.php create mode 100644 lib/Zend/Test/PHPUnit/Db/DataSet/DbRowset.php create mode 100644 lib/Zend/Test/PHPUnit/Db/DataSet/DbTable.php create mode 100644 lib/Zend/Test/PHPUnit/Db/DataSet/DbTableDataSet.php create mode 100644 lib/Zend/Test/PHPUnit/Db/DataSet/QueryDataSet.php create mode 100644 lib/Zend/Test/PHPUnit/Db/DataSet/QueryTable.php create mode 100644 lib/Zend/Test/PHPUnit/Db/Exception.php create mode 100644 lib/Zend/Test/PHPUnit/Db/Metadata/Generic.php create mode 100644 lib/Zend/Test/PHPUnit/Db/Operation/DeleteAll.php create mode 100644 lib/Zend/Test/PHPUnit/Db/Operation/Insert.php create mode 100644 lib/Zend/Test/PHPUnit/Db/Operation/Truncate.php create mode 100644 lib/Zend/Test/PHPUnit/Db/SimpleTester.php create mode 100644 lib/Zend/Tool/Framework/Client/Config.php create mode 100644 lib/Zend/Tool/Framework/Client/Storage.php create mode 100644 lib/Zend/Tool/Framework/Client/Storage/AdapterInterface.php create mode 100644 lib/Zend/Tool/Framework/Client/Storage/Directory.php create mode 100644 lib/Zend/Tool/Project/Context/Content/Engine.php create mode 100644 lib/Zend/Tool/Project/Context/Content/Engine/CodeGenerator.php create mode 100644 lib/Zend/Tool/Project/Context/Content/Engine/Phtml.php create mode 100644 lib/Zend/Translate/Plural.php create mode 100644 lib/Zend/View/Helper/BaseUrl.php create mode 100644 skin/adminhtml/default/default/images/bg_notifications.gif delete mode 100644 skin/adminhtml/default/default/images/bg_severity-critical.gif delete mode 100644 skin/adminhtml/default/default/images/bg_severity-major.gif delete mode 100644 skin/adminhtml/default/default/images/bg_severity-minor.gif delete mode 100644 skin/adminhtml/default/default/images/bg_severity-notice.gif create mode 100644 skin/adminhtml/default/default/images/widget/catalog__category_widget_link.gif create mode 100644 skin/adminhtml/default/default/images/widget/catalog__product_widget_link.gif create mode 100644 skin/adminhtml/default/default/images/widget/catalog__product_widget_new.gif create mode 100644 skin/adminhtml/default/default/images/widget/cms__widget_page_link.gif create mode 100644 skin/adminhtml/default/default/images/widget/default.gif create mode 100644 skin/adminhtml/default/default/images/widget/reports__product_widget_compared.gif create mode 100644 skin/adminhtml/default/default/images/widget/reports__product_widget_viewed.gif create mode 100644 skin/adminhtml/default/default/images/widget_placeholder.gif diff --git a/app/Mage.php b/app/Mage.php index 6702f50c6a..68be8b1bd8 100644 --- a/app/Mage.php +++ b/app/Mage.php @@ -130,13 +130,32 @@ final class Mage static private $_isInstalled; /** - * Retrieve current Magento version + * Gets the current Magento version string + * @link http://www.magentocommerce.com/blog/new-community-edition-release-process/ * * @return string */ public static function getVersion() { - return '1.4.0.0-alpha1'; + return '1.4.0.0-alpha2'; + } + + /** + * Gets the detailed Magento version information + * @link http://www.magentocommerce.com/blog/new-community-edition-release-process/ + * + * @return array + */ + public static function getVersionInfo() + { + return array( + 'major' => '1', + 'minor' => '4', + 'revision' => '0', + 'patch' => '0', + 'stability' => 'alpha', + 'number' => '2', + ); } /** @@ -312,7 +331,7 @@ public static function getStoreConfig($path, $store = null) public static function getStoreConfigFlag($path, $store = null) { $flag = strtolower(self::getStoreConfig($path, $store)); - if (!empty($flag) && 'false'!==$flag && '0'!==$flag) { + if (!empty($flag) && 'false' !== $flag) { return true; } else { return false; @@ -665,8 +684,11 @@ public static function log($message, $level = null, $file = '') if (!self::getConfig()) { return; } - if (!self::getStoreConfig('dev/log/active')) { - return; + + if (!self::$_isDeveloperMode) { + if (!self::getStoreConfig('dev/log/active')) { + return; + } } static $loggers = array(); @@ -768,6 +790,9 @@ public static function printException(Exception $e, $extra = '') !empty($extra) ? $extra . "\n\n" : '' . $e->getMessage(), $e->getTraceAsString() ); + if (isset($_SERVER) && isset($_SERVER['REQUEST_URI'])) { + $reportData[] = $_SERVER['REQUEST_URI']; + } $reportData = serialize($reportData); file_put_contents($reportFile, $reportData); diff --git a/app/code/core/Mage/Admin/Model/Config.php b/app/code/core/Mage/Admin/Model/Config.php index 8681c43321..088f72a1e8 100644 --- a/app/code/core/Mage/Admin/Model/Config.php +++ b/app/code/core/Mage/Admin/Model/Config.php @@ -34,11 +34,46 @@ */ class Mage_Admin_Model_Config extends Varien_Simplexml_Config { + /** + * adminhtml.xml merged config + * + * @var Varien_Simplexml_Config + */ + protected $_adminhtmlConfig; + + /** + * Load config from merged adminhtml.xml files + */ public function __construct() { parent::__construct(); - #$this->_elementClass = 'Mage_Core_Model_Config_Element'; - #$this->loadFile(Mage::getModuleDir('etc', 'Mage_Admin').DS.'admin.xml'); + $this->setCacheId('adminhtml_acl_menu_config'); + /* @var $adminhtmlConfig Varien_Simplexml_Config */ + $adminhtmlConfig = Mage::app()->loadCache($this->getCacheId()); + if ($adminhtmlConfig) { + $this->_adminhtmlConfig = new Varien_Simplexml_Config($adminhtmlConfig); + } else { + $adminhtmlConfig = new Varien_Simplexml_Config; + $adminhtmlConfig->loadString(''); + Mage::getConfig()->loadModulesConfiguration('adminhtml.xml', $adminhtmlConfig); + $this->_adminhtmlConfig = $adminhtmlConfig; + + // support back compatibility with base config + $aclConfig = Mage::getConfig()->getNode('adminhtml/acl'); + if ($aclConfig) { + $adminhtmlConfig->getNode()->extendChild($aclConfig, true); + } + + $menuConfig = Mage::getConfig()->getNode('adminhtml/menu'); + if ($menuConfig) { + $adminhtmlConfig->getNode()->extendChild($menuConfig, true); + } + + if (Mage::app()->useCache('config')) { + Mage::app()->saveCache($adminhtmlConfig->getXmlString(), $this->getCacheId(), + array(Mage_Core_Model_Config::CACHE_TAG)); + } + } } /** @@ -52,7 +87,7 @@ public function __construct() public function loadAclResources(Mage_Admin_Model_Acl $acl, $resource=null, $parentName=null) { if (is_null($resource)) { - $resource = Mage::getConfig()->getNode("adminhtml/acl/resources"); + $resource = $this->getAdminhtmlConfig()->getNode("acl/resources"); $resourceName = null; } else { $resourceName = (is_null($parentName) ? '' : $parentName.'/').$resource->getName(); @@ -121,4 +156,13 @@ public function getAclPrivilegeSet($name='') return false; } + /** + * Retrieve xml config + * + * @return Varien_Simplexml_Config + */ + public function getAdminhtmlConfig() + { + return $this->_adminhtmlConfig; + } } \ No newline at end of file diff --git a/app/code/core/Mage/Admin/Model/Mysql4/Rules.php b/app/code/core/Mage/Admin/Model/Mysql4/Rules.php index 257dde77b3..b3fa0a20ba 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/Rules.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/Rules.php @@ -30,38 +30,47 @@ protected function _construct() { $this->_init('admin/rule', 'rule_id'); } - public function saveRel(Mage_Admin_Model_Rules $rule) { - $this->_getWriteAdapter()->beginTransaction(); - + /** + * 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}"); - $masterResources = Mage::getModel('admin/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; + $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); } } } diff --git a/app/code/core/Mage/Admin/Model/Roles.php b/app/code/core/Mage/Admin/Model/Roles.php index 1902ee42f9..fe49529e22 100644 --- a/app/code/core/Mage/Admin/Model/Roles.php +++ b/app/code/core/Mage/Admin/Model/Roles.php @@ -71,7 +71,7 @@ protected function _buildResourcesArray(Varien_Simplexml_Element $resource=null, { static $result; if (is_null($resource)) { - $resource = Mage::getConfig()->getNode('adminhtml/acl/resources'); + $resource = Mage::getSingleton('admin/config')->getAdminhtmlConfig()->getNode('acl/resources'); $resourceName = null; $level = -1; } else { @@ -89,12 +89,6 @@ protected function _buildResourcesArray(Varien_Simplexml_Element $resource=null, $resource->addAttribute("module_c", $module); } - //if (!(string)$resource->title) { - // return array(); - //} - - //$resource->title = Mage::helper($module)->__((string)$resource->title); - if ( is_null($represent2Darray) ) { $result[$resourceName]['name'] = Mage::helper($module)->__((string)$resource->title); $result[$resourceName]['level'] = $level; diff --git a/app/code/core/Mage/Admin/Model/User.php b/app/code/core/Mage/Admin/Model/User.php index aee3ccb533..8c6407f349 100644 --- a/app/code/core/Mage/Admin/Model/User.php +++ b/app/code/core/Mage/Admin/Model/User.php @@ -56,13 +56,12 @@ protected function _construct() } /** - * Save user + * Processing data before model save * * @return Mage_Admin_Model_User */ - public function save() + protected function _beforeSave() { - $this->_beforeSave(); $data = array( 'firstname' => $this->getFirstname(), 'lastname' => $this->getLastname(), @@ -85,8 +84,7 @@ public function save() if ($this->getNewPassword()) { $data['password'] = $this->_getEncodedPassword($this->getNewPassword()); - } - elseif ($this->getPassword()) { + } elseif ($this->getPassword()) { $data['new_password'] = $this->getPassword(); } @@ -95,9 +93,8 @@ public function save() } $this->addData($data); - $this->_getResource()->save($this); - $this->_afterSave(); - return $this; + + return parent::_beforeSave(); } /** @@ -115,17 +112,6 @@ public function saveExtra($data) return $this; } - /** - * Delete user - * - * @return Mage_Admin_Model_User - */ - public function delete() - { - $this->_getResource()->delete($this); - return $this; - } - /** * Save user roles * @@ -323,7 +309,7 @@ protected function _getEncodedPassword($pwd) public function findFirstAvailableMenu($parent=null, $path='', $level=0) { if ($parent == null) { - $parent = Mage::getConfig()->getNode('adminhtml/menu'); + $parent = Mage::getSingleton('admin/config')->getAdminhtmlConfig()->getNode('menu'); } foreach ($parent->children() as $childName=>$child) { $aclResource = 'admin/' . $path . $childName; @@ -372,8 +358,9 @@ public function getStartupPageUrl() $startupPage = Mage::getStoreConfig(self::XML_PATH_STARTUP_PAGE); $aclResource = 'admin/' . $startupPage; if (Mage::getSingleton('admin/session')->isAllowed($aclResource)) { - $nodePath = 'adminhtml/menu/' . join('/children/', explode('/', $startupPage)) . '/action'; - if ($url = Mage::getConfig()->getNode($nodePath)) { + $nodePath = 'menu/' . join('/children/', explode('/', $startupPage)) . '/action'; + $url = Mage::getSingleton('admin/config')->getAdminhtmlConfig()->getNode($nodePath); + if ($url) { return $url; } } diff --git a/app/code/core/Mage/Admin/etc/config.xml b/app/code/core/Mage/Admin/etc/config.xml index 2a7e371ce3..686d5b1291 100644 --- a/app/code/core/Mage/Admin/etc/config.xml +++ b/app/code/core/Mage/Admin/etc/config.xml @@ -54,16 +54,7 @@ Mage_Admin - - core_setup - - - core_write - - - core_read - Mage_Admin_Block diff --git a/app/code/core/Mage/AdminNotification/Model/Observer.php b/app/code/core/Mage/AdminNotification/Model/Observer.php index fb56f37380..fdfac46446 100644 --- a/app/code/core/Mage/AdminNotification/Model/Observer.php +++ b/app/code/core/Mage/AdminNotification/Model/Observer.php @@ -41,9 +41,15 @@ class Mage_AdminNotification_Model_Observer */ public function preDispatch(Varien_Event_Observer $observer) { - $feedModel = Mage::getModel('adminnotification/feed'); - /* @var $feedModel Mage_AdminNotification_Model_Feed */ - $feedModel->checkUpdate(); + if (Mage::getSingleton('admin/session')->isLoggedIn()) { + + $feedModel = Mage::getModel('adminnotification/feed'); + /* @var $feedModel Mage_AdminNotification_Model_Feed */ + + $feedModel->checkUpdate(); + + } + } } \ No newline at end of file diff --git a/app/code/core/Mage/AdminNotification/etc/adminhtml.xml b/app/code/core/Mage/AdminNotification/etc/adminhtml.xml new file mode 100644 index 0000000000..28a4dd0812 --- /dev/null +++ b/app/code/core/Mage/AdminNotification/etc/adminhtml.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + Notifications + 10 + + + Show Toolbar + 10 + + + Show List + 20 + + + Mark as read + 30 + + + Remove + 40 + + + + + + + + + + + + + + Notifications + adminhtml/notification + 15 + + + + + diff --git a/app/code/core/Mage/AdminNotification/etc/config.xml b/app/code/core/Mage/AdminNotification/etc/config.xml index b5673be53d..62404ad191 100644 --- a/app/code/core/Mage/AdminNotification/etc/config.xml +++ b/app/code/core/Mage/AdminNotification/etc/config.xml @@ -57,20 +57,7 @@ Mage_AdminNotification - - core_setup - - - - core_setup - - - - - core_setup - - @@ -80,51 +67,6 @@ - - - - - - - - Notifications - 10 - - - Show Toolbar - 10 - - - Show List - 20 - - - Mark as read - 30 - - - Remove - 40 - - - - - - - - - - - - - - Notifications - adminhtml/notification - 15 - - - - @@ -138,7 +80,6 @@ - singleton adminnotification/observer preDispatch 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 54f186078e..b02f24424c 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 @@ -94,7 +94,7 @@ protected function _prepareForm() 'label' => Mage::helper('adminhtml')->__('New Api Key'), 'id' => 'new_pass', 'title' => Mage::helper('adminhtml')->__('New Api Key'), - 'class' => 'input-text validate-admin-password', + 'class' => 'input-text validate-password', )); $fieldset->addField('confirmation', 'password', array( @@ -110,7 +110,7 @@ protected function _prepareForm() 'label' => Mage::helper('adminhtml')->__('Api Key'), 'id' => 'customer_pass', 'title' => Mage::helper('adminhtml')->__('Api Key'), - 'class' => 'input-text required-entry validate-admin-password', + 'class' => 'input-text required-entry validate-password', 'required' => true, )); $fieldset->addField('confirmation', 'password', array( diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Category/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Category/Edit/Form.php index c79ba06c52..5b608bf301 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Category/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Category/Edit/Form.php @@ -145,6 +145,9 @@ public function getAdditionalButtonsHtml() */ public function addAdditionalButton($alias, $config) { + if (isset($config['name'])) { + $config['element_name'] = $config['name']; + } $this->setChild($alias . '_button', $this->getLayout()->createBlock('adminhtml/widget_button')->addData($config)); $this->_additionalButtons[$alias] = $alias . '_button'; diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Category/Widget/Chooser.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Category/Widget/Chooser.php new file mode 100644 index 0000000000..cea17d37bb --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Category/Widget/Chooser.php @@ -0,0 +1,173 @@ + + */ +class Mage_Adminhtml_Block_Catalog_Category_Widget_Chooser extends Mage_Adminhtml_Block_Catalog_Category_Tree +{ + protected $_selectedCategories = array(); + + /** + * Block construction + * Defines tree template and init tree params + */ + public function __construct() + { + parent::__construct(); + $this->setTemplate('catalog/category/widget/tree.phtml'); + $this->_withProductCount = false; + } + + /** + * Setter + * + * @param array $selectedCategories + * @return Mage_Adminhtml_Block_Catalog_Category_Widget_Chooser + */ + public function setSelectedCategories($selectedCategories) + { + $this->_selectedCategories = $selectedCategories; + return $this; + } + + /** + * Getter + * + * @return array + */ + public function getSelectedCategories() + { + return $this->_selectedCategories; + } + + /** + * Prepare chooser element HTML + * + * @param Varien_Data_Form_Element_Abstract $element Form Element + * @return Varien_Data_Form_Element_Abstract + */ + public function prepareElementHtml(Varien_Data_Form_Element_Abstract $element) + { + $uniqId = $element->getId() . md5(microtime()); + $sourceUrl = $this->getUrl('*/catalog_category_widget/chooser', array('uniq_id' => $uniqId, 'use_massaction' => false)); + + $chooser = $this->getLayout()->createBlock('adminhtml/cms_widget_chooser') + ->setElement($element) + ->setTranslationHelper($this->getTranslationHelper()) + ->setConfig($this->getConfig()) + ->setFieldsetId($this->getFieldsetId()) + ->setSourceUrl($sourceUrl) + ->setUniqId($uniqId); + + if ($element->getValue()) { + $value = explode('/', $element->getValue()); + $categoryId = isset($value[1]) ? $value[1] : false; + if ($categoryId) { + $label = Mage::getSingleton('catalog/category')->load($categoryId)->getName(); + $chooser->setLabel($label); + } + } + + $element->setData('after_element_html', $chooser->toHtml()); + return $element; + } + + /** + * Category Tree node onClick listener js function + * + * @return string + */ + public function getNodeClickListener() + { + if ($this->getData('node_click_listener')) { + return $this->getData('node_click_listener'); + } + if ($this->getUseMassaction()) { + $js = ' + function (node, e) { + node.ui.toggleCheck(true); + } + '; + } else { + $chooserJsObject = $this->getId(); + $js = ' + function (node, e) { + '.$chooserJsObject.'.setElementValue("category/" + node.attributes.id); + '.$chooserJsObject.'.setElementLabel(node.text); + '.$chooserJsObject.'.close(); + } + '; + } + return $js; + } + + /** + * Get JSON of a tree node or an associative array + * + * @param Varien_Data_Tree_Node|array $node + * @param int $level + * @return string + */ + protected function _getNodeJson($node, $level = 0) + { + $item = parent::_getNodeJson($node, $level); + if (in_array($node->getId(), $this->getSelectedCategories())) { + $item['checked'] = true; + } + $item['is_anchor'] = (int)$node->getIsAnchor(); + $item['url_key'] = $node->getData('url_key'); + return $item; + } + + /** + * Adds some extra params to categories collection + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection + */ + public function getCategoryCollection() + { + return parent::getCategoryCollection()->addAttributeToSelect('url_key')->addAttributeToSelect('is_anchor'); + } + + /** + * Tree JSON source URL + * + * @return string + */ + public function getLoadTreeUrl($expanded=null) + { + return $this->getUrl('*/catalog_category_widget/categoriesJson', array( + '_current'=>true, + 'uniq_id' => $this->getId(), + 'use_massaction' => $this->getUseMassaction() + )); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Form/Renderer/Fieldset/Element.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Form/Renderer/Fieldset/Element.php index 180b6f3b1c..a0edfb0af7 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Form/Renderer/Fieldset/Element.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Form/Renderer/Fieldset/Element.php @@ -97,7 +97,7 @@ public function canDisplayUseDefault() public function usedDefault() { $devaultValue = $this->getDataObject()->getAttributeDefaultValue($this->getAttribute()->getAttributeCode()); - return is_null($devaultValue); + return $devaultValue === false; } /** diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Edit/Tab/Main.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Edit/Tab/Main.php index 8526e7d60b..c8edeaf5f4 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Edit/Tab/Main.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Edit/Tab/Main.php @@ -30,70 +30,45 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Catalog_Product_Attribute_Edit_Tab_Main extends Mage_Adminhtml_Block_Widget_Form +class Mage_Adminhtml_Block_Catalog_Product_Attribute_Edit_Tab_Main extends Mage_Eav_Block_Adminhtml_Attribute_Edit_Main_Abstract { - + /** + * Adding product form elements for editing attribute + * + * @return Mage_Adminhtml_Block_Catalog_Product_Attribute_Edit_Tab_Main + */ protected function _prepareForm() { - $model = Mage::registry('entity_attribute'); - - $form = new Varien_Data_Form(array( - 'id' => 'edit_form', - 'action' => $this->getData('action'), - 'method' => 'post' - )); - - $disableAttributeFields = array( - 'sku' => array( - 'is_global', - 'is_unique', - ), - 'url_key' => array( - 'is_unique', - ), - 'status' => array( - 'is_configurable', - 'is_filterable', - 'is_filterable_in_search' - ), - 'visibility' => array( - 'is_configurable', - 'is_filterable', - 'is_filterable_in_search' - ), - ); - - $fieldset = $form->addFieldset('base_fieldset', - array('legend'=>Mage::helper('catalog')->__('Attribute Properties')) - ); - if ($model->getAttributeId()) { - $fieldset->addField('attribute_id', 'hidden', array( - 'name' => 'attribute_id', - )); - } - - $this->_addElementTypes($fieldset); - - $yesno = array( + parent::_prepareForm(); + $attributeObject = $this->getAttributeObject(); + /* @var $form Varien_Data_Form */ + $form = $this->getForm(); + /* @var $fieldset Varien_Data_Form_Element_Fieldset */ + $fieldset = $form->getElement('base_fieldset'); + + $frontendInputElm = $form->getElement('frontend_input'); + $additionalTypes = array( array( - 'value' => 0, - 'label' => Mage::helper('catalog')->__('No') + 'value' => 'price', + 'label' => Mage::helper('catalog')->__('Price') ), array( - 'value' => 1, - 'label' => Mage::helper('catalog')->__('Yes') - )); + 'value' => 'media_image', + 'label' => Mage::helper('catalog')->__('Media Image') + ) + ); + if ($attributeObject->getFrontendInput() == 'gallery') { + $inputTypes[] = array( + 'value' => 'gallery', + 'label' => Mage::helper('catalog')->__('Gallery') + ); + } + $frontendInputValues = array_merge($frontendInputElm->getValues(), $additionalTypes); + $frontendInputElm->setValues($frontendInputValues); - $fieldset->addField('attribute_code', 'text', array( - 'name' => 'attribute_code', - 'label' => Mage::helper('catalog')->__('Attribute Code'), - 'title' => Mage::helper('catalog')->__('Attribute Code'), - 'note' => Mage::helper('catalog')->__('For internal use. Must be unique with no spaces'), - 'class' => 'validate-code', - 'required' => true, - )); + $yesnoSource = Mage::getModel('adminhtml/system_config_source_yesno')->toOptionArray(); $scopes = array( Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE =>Mage::helper('catalog')->__('Store View'), @@ -101,7 +76,7 @@ protected function _prepareForm() Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL =>Mage::helper('catalog')->__('Global'), ); - if ($model->getAttributeCode() == 'status' || $model->getAttributeCode() == 'tax_class_id') { + if ($attributeObject->getAttributeCode() == 'status' || $attributeObject->getAttributeCode() == 'tax_class_id') { unset($scopes[Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE]); } @@ -111,165 +86,7 @@ protected function _prepareForm() 'title' => Mage::helper('catalog')->__('Scope'), 'note' => Mage::helper('catalog')->__('Declare attribute value saving scope'), 'values'=> $scopes - )); - - $inputTypes = array( - array( - 'value' => 'text', - 'label' => Mage::helper('catalog')->__('Text Field') - ), - array( - 'value' => 'textarea', - 'label' => Mage::helper('catalog')->__('Text Area') - ), - array( - 'value' => 'date', - 'label' => Mage::helper('catalog')->__('Date') - ), - array( - 'value' => 'boolean', - 'label' => Mage::helper('catalog')->__('Yes/No') - ), - array( - 'value' => 'multiselect', - 'label' => Mage::helper('catalog')->__('Multiple Select') - ), - array( - 'value' => 'select', - 'label' => Mage::helper('catalog')->__('Dropdown') - ), - array( - 'value' => 'price', - 'label' => Mage::helper('catalog')->__('Price') - ), - array( - 'value' => 'media_image', - 'label' => Mage::helper('catalog')->__('Media Image') - ), - ); - - if ($model->getFrontendInput() == 'gallery') { - $inputTypes[] = array( - 'value' => 'gallery', - 'label' => Mage::helper('catalog')->__('Gallery') - ); - } - - $response = new Varien_Object(); - $response->setTypes(array()); - Mage::dispatchEvent('adminhtml_product_attribute_types', array('response'=>$response)); - - $_disabledTypes = array(); - $_hiddenFields = array(); - foreach ($response->getTypes() as $type) { - $inputTypes[] = $type; - if (isset($type['hide_fields'])) { - $_hiddenFields[$type['value']] = $type['hide_fields']; - } - if (isset($type['disabled_types'])) { - $_disabledTypes[$type['value']] = $type['disabled_types']; - } - } - Mage::register('attribute_type_hidden_fields', $_hiddenFields); - Mage::register('attribute_type_disabled_types', $_disabledTypes); - - - $fieldset->addField('frontend_input', 'select', array( - 'name' => 'frontend_input', - 'label' => Mage::helper('catalog')->__('Catalog Input Type for Store Owner'), - 'title' => Mage::helper('catalog')->__('Catalog Input Type for Store Owner'), - 'value' => 'text', - 'values'=> $inputTypes - )); - - $fieldset->addField('default_value_text', 'text', array( - 'name' => 'default_value_text', - 'label' => Mage::helper('catalog')->__('Default value'), - 'title' => Mage::helper('catalog')->__('Default value'), - 'value' => $model->getDefaultValue(), - )); - - $fieldset->addField('default_value_yesno', 'select', array( - 'name' => 'default_value_yesno', - 'label' => Mage::helper('catalog')->__('Default value'), - 'title' => Mage::helper('catalog')->__('Default value'), - 'values' => $yesno, - 'value' => $model->getDefaultValue(), - )); - - $dateFormatIso = Mage::app()->getLocale()->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT); - $fieldset->addField('default_value_date', 'date', array( - 'name' => 'default_value_date', - 'label' => Mage::helper('catalog')->__('Default value'), - 'title' => Mage::helper('catalog')->__('Default value'), - 'image' => $this->getSkinUrl('images/grid-cal.gif'), - 'value' => $model->getDefaultValue(), - 'format' => $dateFormatIso - )); - - $fieldset->addField('default_value_textarea', 'textarea', array( - 'name' => 'default_value_textarea', - 'label' => Mage::helper('catalog')->__('Default value'), - 'title' => Mage::helper('catalog')->__('Default value'), - 'value' => $model->getDefaultValue(), - )); - - $fieldset->addField('is_unique', 'select', array( - 'name' => 'is_unique', - 'label' => Mage::helper('catalog')->__('Unique Value'), - 'title' => Mage::helper('catalog')->__('Unique Value (not shared with other products)'), - 'note' => Mage::helper('catalog')->__('Not shared with other products'), - 'values' => $yesno, - )); - - $fieldset->addField('is_required', 'select', array( - 'name' => 'is_required', - 'label' => Mage::helper('catalog')->__('Values Required'), - 'title' => Mage::helper('catalog')->__('Values Required'), - 'values' => $yesno, - )); - - $fieldset->addField('frontend_class', 'select', array( - 'name' => 'frontend_class', - 'label' => Mage::helper('catalog')->__('Input Validation for Store Owner'), - 'title' => Mage::helper('catalog')->__('Input Validation for Store Owner'), - 'values'=> array( - array( - 'value' => '', - 'label' => Mage::helper('catalog')->__('None') - ), - array( - 'value' => 'validate-number', - 'label' => Mage::helper('catalog')->__('Decimal Number') - ), - array( - 'value' => 'validate-digits', - 'label' => Mage::helper('catalog')->__('Integer Number') - ), - array( - 'value' => 'validate-email', - 'label' => Mage::helper('catalog')->__('Email') - ), - array( - 'value' => 'validate-url', - 'label' => Mage::helper('catalog')->__('Url') - ), - array( - 'value' => 'validate-alpha', - 'label' => Mage::helper('catalog')->__('Letters') - ), - array( - 'value' => 'validate-alphanum', - 'label' => Mage::helper('catalog')->__('Letters(a-zA-Z) or Numbers(0-9)') - ), - ) - )); -/* - $fieldset->addField('use_in_super_product', 'select', array( - 'name' => 'use_in_super_product', - 'label' => Mage::helper('catalog')->__('Apply To Configurable/Grouped Product'), - 'values' => $yesno, - )); */ + ), 'attribute_code'); $fieldset->addField('apply_to', 'apply', array( 'name' => 'apply_to[]', @@ -280,15 +97,13 @@ protected function _prepareForm() 'custom' => Mage::helper('catalog')->__('Selected Product Types') ), 'required' => true - )); + ), 'frontend_class'); $fieldset->addField('is_configurable', 'select', array( 'name' => 'is_configurable', 'label' => Mage::helper('catalog')->__('Use To Create Configurable Product'), - 'values' => $yesno, - )); - // ----- - + 'values' => $yesnoSource, + ), 'apply_to'); // frontend properties fieldset $fieldset = $form->addFieldset('front_fieldset', array('legend'=>Mage::helper('catalog')->__('Frontend Properties'))); @@ -297,24 +112,23 @@ protected function _prepareForm() 'name' => 'is_searchable', 'label' => Mage::helper('catalog')->__('Use in quick search'), 'title' => Mage::helper('catalog')->__('Use in quick search'), - 'values' => $yesno, + 'values' => $yesnoSource, )); $fieldset->addField('is_visible_in_advanced_search', 'select', array( 'name' => 'is_visible_in_advanced_search', 'label' => Mage::helper('catalog')->__('Use in advanced search'), 'title' => Mage::helper('catalog')->__('Use in advanced search'), - 'values' => $yesno, + 'values' => $yesnoSource, )); $fieldset->addField('is_comparable', 'select', array( 'name' => 'is_comparable', 'label' => Mage::helper('catalog')->__('Comparable on Front-end'), 'title' => Mage::helper('catalog')->__('Comparable on Front-end'), - 'values' => $yesno, + 'values' => $yesnoSource, )); - $fieldset->addField('is_filterable', 'select', array( 'name' => 'is_filterable', 'label' => Mage::helper('catalog')->__("Use In Layered Navigation"), @@ -332,14 +146,14 @@ protected function _prepareForm() 'label' => Mage::helper('catalog')->__("Use In Search Results Layered Navigation"), 'title' => Mage::helper('catalog')->__('Can be used only with catalog input type Dropdown, Multiple Select and Price'), 'note' => Mage::helper('catalog')->__('Can be used only with catalog input type Dropdown, Multiple Select and Price'), - 'values' => $yesno, + 'values' => $yesnoSource, )); $fieldset->addField('is_used_for_price_rules', 'select', array( 'name' => 'is_used_for_price_rules', 'label' => Mage::helper('catalog')->__('Use for Price Rule Conditions'), 'title' => Mage::helper('catalog')->__('Use for Price Rule Conditions'), - 'values' => $yesno, + 'values' => $yesnoSource, )); $fieldset->addField('position', 'text', array( @@ -354,9 +168,9 @@ protected function _prepareForm() 'name' => 'is_html_allowed_on_front', 'label' => Mage::helper('catalog')->__('Allow HTML-tags on Front-end'), 'title' => Mage::helper('catalog')->__('Allow HTML-tags on Front-end'), - 'values' => $yesno, + 'values' => $yesnoSource, )); - if (!$model->getId()) { + if (!$attributeObject->getId()) { $htmlAllowed->setValue(1); } @@ -364,7 +178,7 @@ protected function _prepareForm() 'name' => 'is_visible_on_front', 'label' => Mage::helper('catalog')->__('Visible on Product View Page on Front-end'), 'title' => Mage::helper('catalog')->__('Visible on Product View Page on Front-end'), - 'values' => $yesno, + 'values' => $yesnoSource, )); $fieldset->addField('used_in_product_listing', 'select', array( @@ -372,52 +186,54 @@ protected function _prepareForm() 'label' => Mage::helper('catalog')->__('Used in product listing'), 'title' => Mage::helper('catalog')->__('Used in product listing'), 'note' => Mage::helper('catalog')->__('Depends on design theme'), - 'values' => $yesno, + 'values' => $yesnoSource, )); $fieldset->addField('used_for_sort_by', 'select', array( 'name' => 'used_for_sort_by', 'label' => Mage::helper('catalog')->__('Used for sorting in product listing'), 'title' => Mage::helper('catalog')->__('Used for sorting in product listing'), 'note' => Mage::helper('catalog')->__('Depends on design theme'), - 'values' => $yesno, + 'values' => $yesnoSource, )); - if ($model->getId()) { - $form->getElement('attribute_code')->setDisabled(1); - $form->getElement('frontend_input')->setDisabled(1); - - if (isset($disableAttributeFields[$model->getAttributeCode()])) { - foreach ($disableAttributeFields[$model->getAttributeCode()] as $field) { - $form->getElement($field)->setDisabled(1); - $form->getElement($field)->setReadonly(1); - } - } - } - if (!$model->getIsUserDefined() && $model->getId()) { - $form->getElement('is_unique')->setDisabled(1); - } - - $form->addValues($model->getData()); - $form->getElement('apply_to')->setSize(5); - if ($applyTo = $model->getApplyTo()) { + if ($applyTo = $attributeObject->getApplyTo()) { $applyTo = is_array($applyTo) ? $applyTo : explode(',', $applyTo); $form->getElement('apply_to')->setValue($applyTo); } else { $form->getElement('apply_to')->addClass('no-display ignore-validate'); } - $this->setForm($form); + $response = new Varien_Object(); + $response->setTypes(array()); + Mage::dispatchEvent('adminhtml_product_attribute_types', array('response'=>$response)); + $_disabledTypes = array(); + $_hiddenFields = array(); + foreach ($response->getTypes() as $type) { + $inputTypes[] = $type; + if (isset($type['hide_fields'])) { + $_hiddenFields[$type['value']] = $type['hide_fields']; + } + if (isset($type['disabled_types'])) { + $_disabledTypes[$type['value']] = $type['disabled_types']; + } + } + Mage::register('attribute_type_hidden_fields', $_hiddenFields); + Mage::register('attribute_type_disabled_types', $_disabledTypes); - return parent::_prepareForm(); + return $this; } + /** + * Retrieve additional element types for product attributes + * + * @return array + */ protected function _getAdditionalElementTypes() { return array( 'apply' => Mage::getConfig()->getBlockClassName('adminhtml/catalog_product_helper_form_apply') ); } - } diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Edit/Tab/Options.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Edit/Tab/Options.php index 0e3d17b042..637333b377 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Edit/Tab/Options.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Edit/Tab/Options.php @@ -31,155 +31,6 @@ * @package Mage_Adminhtml * @author Magento Core Team */ -class Mage_Adminhtml_Block_Catalog_Product_Attribute_Edit_Tab_Options extends Mage_Adminhtml_Block_Widget +class Mage_Adminhtml_Block_Catalog_Product_Attribute_Edit_Tab_Options extends Mage_Eav_Block_Adminhtml_Attribute_Edit_Options_Abstract { - - public function __construct() - { - parent::__construct(); - $this->setTemplate('catalog/product/attribute/options.phtml'); - } - - protected function _prepareLayout() - { - $this->setChild('delete_button', - $this->getLayout()->createBlock('adminhtml/widget_button') - ->setData(array( - 'label' => Mage::helper('catalog')->__('Delete'), - 'class' => 'delete delete-option' - ))); - - $this->setChild('add_button', - $this->getLayout()->createBlock('adminhtml/widget_button') - ->setData(array( - 'label' => Mage::helper('catalog')->__('Add Option'), - 'class' => 'add', - 'id' => 'add_new_option_button' - ))); - return parent::_prepareLayout(); - } - - public function getDeleteButtonHtml() - { - return $this->getChildHtml('delete_button'); - } - - public function getAddNewButtonHtml() - { - return $this->getChildHtml('add_button'); - } - - public function getStores() - { - $stores = $this->getData('stores'); - if (is_null($stores)) { - $stores = Mage::getModel('core/store') - ->getResourceCollection() - ->setLoadDefault(true) - ->load(); - $this->setData('stores', $stores); - } - return $stores; - } - - public function getOptionValues() - { - $attributeType = $this->getAttributeObject()->getFrontendInput(); - $defaultValues = $this->getAttributeObject()->getDefaultValue(); - if ($attributeType == 'select' || $attributeType == 'multiselect') { - $defaultValues = explode(',', $defaultValues); - } else { - $defaultValues = array(); - } - - switch ($attributeType) { - case 'select': - $inputType = 'radio'; - break; - case 'multiselect': - $inputType = 'checkbox'; - break; - default: - $inputType = ''; - break; - } - - $values = $this->getData('option_values'); - if (is_null($values)) { - $values = array(); - $optionCollection = Mage::getResourceModel('eav/entity_attribute_option_collection') - ->setAttributeFilter($this->getAttributeObject()->getId()) - ->setPositionOrder('desc', true) - ->load(); - - foreach ($optionCollection as $option) { - $value = array(); - if (in_array($option->getId(), $defaultValues)) { - $value['checked'] = 'checked="checked"'; - } else { - $value['checked'] = ''; - } - - $value['intype'] = $inputType; - $value['id'] = $option->getId(); - $value['sort_order'] = $option->getSortOrder(); - foreach ($this->getStores() as $store) { - $storeValues = $this->getStoreOptionValues($store->getId()); - if (isset($storeValues[$option->getId()])) { - $value['store'.$store->getId()] = htmlspecialchars($storeValues[$option->getId()]); - } - else { - $value['store'.$store->getId()] = ''; - } - } - $values[] = new Varien_Object($value); - } - $this->setData('option_values', $values); - } - - return $values; - } - - public function getLabelValues() - { - $values = array(); - $values[0] = $this->getAttributeObject()->getFrontend()->getLabel(); - // it can be array and cause bug - $frontendLabel = $this->getAttributeObject()->getFrontend()->getLabel(); - if (is_array($frontendLabel)) { - $frontendLabel = array_shift($frontendLabel); - } - $translations = Mage::getModel('core/translate_string') - ->load(Mage_Catalog_Model_Entity_Attribute::MODULE_NAME.Mage_Core_Model_Translate::SCOPE_SEPARATOR.$frontendLabel) - ->getStoreTranslations(); - foreach ($this->getStores() as $store) { - if ($store->getId() != 0) { - $values[$store->getId()] = isset($translations[$store->getId()]) ? $translations[$store->getId()] : ''; - } - } - return $values; - } - - public function getStoreOptionValues($storeId) - { - $values = $this->getData('store_option_values_'.$storeId); - if (is_null($values)) { - $values = array(); - $valuesCollection = Mage::getResourceModel('eav/entity_attribute_option_collection') - ->setAttributeFilter($this->getAttributeObject()->getId()) - ->setStoreFilter($storeId, false) - ->load(); - foreach ($valuesCollection as $item) { - $values[$item->getId()] = $item->getValue(); - } - $this->setData('store_option_values_'.$storeId, $values); - } - return $values; - } - - public function getAttributeObject() - { - return Mage::registry('entity_attribute'); - } - } diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Grid.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Grid.php index 6b7a39d803..2137742cc2 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Grid.php @@ -29,54 +29,34 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Catalog_Product_Attribute_Grid extends Mage_Adminhtml_Block_Widget_Grid +class Mage_Adminhtml_Block_Catalog_Product_Attribute_Grid extends Mage_Eav_Block_Adminhtml_Attribute_Grid_Abstract { - - public function __construct() - { - parent::__construct(); - $this->setId('attributeGrid'); - $this->setDefaultSort('attribute_code'); - $this->setDefaultDir('ASC'); - } - + /** + * Prepare product attributes grid collection object + * + * @return Mage_Adminhtml_Block_Catalog_Product_Attribute_Grid + */ protected function _prepareCollection() { - $collection = Mage::getResourceModel('eav/entity_attribute_collection') - ->setEntityTypeFilter( Mage::getModel('eav/entity')->setType('catalog_product')->getTypeId() ) + $collection = Mage::getResourceModel('catalog/product_attribute_collection') ->addVisibleFilter(); $this->setCollection($collection); return parent::_prepareCollection(); } + /** + * Prepare product attributes grid columns + * + * @return Mage_Adminhtml_Block_Catalog_Product_Attribute_Grid + */ protected function _prepareColumns() { - /* - $this->addColumn('attribute_id', array( - 'header'=>Mage::helper('catalog')->__('ID'), - 'align'=>'right', - 'sortable'=>true, - 'width' => '50px', - 'index'=>'attribute_id' - )); - */ - - $this->addColumn('attribute_code', array( - 'header'=>Mage::helper('catalog')->__('Attribute Code'), - 'sortable'=>true, - 'index'=>'attribute_code' - )); + parent::_prepareColumns(); - $this->addColumn('frontend_label', array( - 'header'=>Mage::helper('catalog')->__('Attribute Label'), - 'sortable'=>true, - 'index'=>'frontend_label' - )); - - $this->addColumn('is_visible', array( + $this->addColumnAfter('is_visible', array( 'header'=>Mage::helper('catalog')->__('Visible'), 'sortable'=>true, 'index'=>'is_visible_on_front', @@ -86,9 +66,9 @@ protected function _prepareColumns() '0' => Mage::helper('catalog')->__('No'), ), 'align' => 'center', - )); + ), 'frontend_label'); - $this->addColumn('is_global', array( + $this->addColumnAfter('is_global', array( 'header'=>Mage::helper('catalog')->__('Scope'), 'sortable'=>true, 'index'=>'is_global', @@ -99,31 +79,7 @@ protected function _prepareColumns() Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL =>Mage::helper('catalog')->__('Global'), ), 'align' => 'center', - )); - - $this->addColumn('is_required', array( - 'header'=>Mage::helper('catalog')->__('Required'), - 'sortable'=>true, - 'index'=>'is_required', - 'type' => 'options', - 'options' => array( - '1' => Mage::helper('catalog')->__('Yes'), - '0' => Mage::helper('catalog')->__('No'), - ), - 'align' => 'center', - )); - - $this->addColumn('is_user_defined', array( - 'header'=>Mage::helper('catalog')->__('System'), - 'sortable'=>true, - 'index'=>'is_user_defined', - 'type' => 'options', - 'align' => 'center', - 'options' => array( - '0' => Mage::helper('catalog')->__('Yes'), // intended reverted use - '1' => Mage::helper('catalog')->__('No'), // intended reverted use - ), - )); + ), 'is_visible'); $this->addColumn('is_searchable', array( 'header'=>Mage::helper('catalog')->__('Searchable'), @@ -135,9 +91,9 @@ protected function _prepareColumns() '0' => Mage::helper('catalog')->__('No'), ), 'align' => 'center', - )); + ), 'is_user_defined'); - $this->addColumn('is_filterable', array( + $this->addColumnAfter('is_filterable', array( 'header'=>Mage::helper('catalog')->__('Use In Layered Navigation'), 'sortable'=>true, 'index'=>'is_filterable', @@ -148,9 +104,9 @@ protected function _prepareColumns() '0' => Mage::helper('catalog')->__('No'), ), 'align' => 'center', - )); + ), 'is_searchable'); - $this->addColumn('is_comparable', array( + $this->addColumnAfter('is_comparable', array( 'header'=>Mage::helper('catalog')->__('Comparable'), 'sortable'=>true, 'index'=>'is_comparable', @@ -160,14 +116,8 @@ protected function _prepareColumns() '0' => Mage::helper('catalog')->__('No'), ), 'align' => 'center', - )); - - return parent::_prepareColumns(); - } + ), 'is_filterable'); - public function getRowUrl($row) - { - return $this->getUrl('*/*/edit', array('attribute_id' => $row->getAttributeId())); + return $this; } - } \ No newline at end of file diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Set/Main.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Set/Main.php index bca0c3bbcb..91a083cd99 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Set/Main.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Set/Main.php @@ -187,8 +187,7 @@ public function getGroupTreeJson() $item['allowDrop'] = true; $item['allowDrag'] = true; - $nodeChildren = Mage::getModel('eav/entity_attribute') - ->getResourceCollection() + $nodeChildren = Mage::getResourceModel('catalog/product_attribute_collection') ->setAttributeGroupFilter($node->getId()) ->addVisibleFilter() ->checkConfigurableProducts() @@ -207,7 +206,7 @@ public function getGroupTreeJson() 'leaf' => true, 'is_user_defined' => $child->getIsUserDefined(), 'is_configurable' => (int)in_array($child->getAttributeId(), $configurable), - 'entity_id' => $child->getEntityId() + 'entity_id' => $child->getEntityAttributeId() ); $item['children'][] = $attr; @@ -230,8 +229,7 @@ public function getAttributeTreeJson() $items = array(); $setId = $this->_getSetId(); - $collection = Mage::getModel('eav/entity_attribute') - ->getResourceCollection() + $collection = Mage::getResourceModel('catalog/product_attribute_collection') ->setAttributeSetFilter($setId) ->load(); @@ -241,9 +239,7 @@ public function getAttributeTreeJson() $attributesIds[] = $item->getAttributeId(); } - $attributes = Mage::getModel('eav/entity_attribute') - ->getResourceCollection() - ->setEntityTypeFilter(Mage::registry('entityType')) + $attributes = Mage::getResourceModel('catalog/product_attribute_collection') ->setAttributesExcludeFilter($attributesIds) ->addVisibleFilter() ->load(); diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Set/Main/Formset.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Set/Main/Formset.php index ea29de7b9e..c5678f1440 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Set/Main/Formset.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Set/Main/Formset.php @@ -43,7 +43,7 @@ protected function _prepareForm() ->load($this->getRequest()->getParam('id')); $form = new Varien_Data_Form(); - $fieldset = $form->addFieldset('set_name', array('legend'=>__('Edit Set Name'))); + $fieldset = $form->addFieldset('set_name', array('legend'=> Mage::helper('catalog')->__('Edit Set Name'))); $fieldset->addField('attribute_set_name', 'text', array( 'label' => Mage::helper('catalog')->__('Name(For internal use)'), diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Ajax/Serializer.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Ajax/Serializer.php index 7200b4bbd8..3a48bb52ac 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Ajax/Serializer.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Ajax/Serializer.php @@ -52,4 +52,20 @@ public function getProductsJSON() } return $result ? Zend_Json_Encoder::encode($result) : '{}'; } + + /** + * Initialize grid block under the "Related Products", "Up-sells", "Cross-sells" sections + * + * @param string $blockName + * @param string $getProductFunction + * @param string $inputName + */ + public function initSerializerBlock($blockName, $getProductFunction, $inputName) + { + if ($block = $this->getLayout()->getBlock($blockName)) { + $this->setGridBlock($block) + ->setProducts(Mage::registry('current_product')->$getProductFunction()) + ->setInputElementName($inputName); + } + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Crosssell.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Crosssell.php index bf7d8330ed..08e8578211 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Crosssell.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Crosssell.php @@ -33,6 +33,10 @@ */ class Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Crosssell extends Mage_Adminhtml_Block_Widget_Grid { + /** + * Set grid params + * + */ public function __construct() { parent::__construct(); @@ -54,6 +58,12 @@ protected function _getProduct() return Mage::registry('current_product'); } + /** + * Add filter + * + * @param object $column + * @return Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Crosssell + */ protected function _addColumnFilterToCollection($column) { // Set custom filter for in product flag @@ -64,19 +74,22 @@ protected function _addColumnFilterToCollection($column) } if ($column->getFilter()->getValue()) { $this->getCollection()->addFieldToFilter('entity_id', array('in'=>$productIds)); - } - else { + } else { if($productIds) { $this->getCollection()->addFieldToFilter('entity_id', array('nin'=>$productIds)); } } - } - else { + } else { parent::_addColumnFilterToCollection($column); } return $this; } + /** + * Prepare collection + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ protected function _prepareCollection() { $collection = Mage::getModel('catalog/product_link')->useCrossSellLinks() @@ -107,37 +120,42 @@ public function isReadonly() return $this->_getProduct()->getCrosssellReadonly(); } + /** + * Add columns to grid + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ protected function _prepareColumns() { if (!$this->isReadonly()) { $this->addColumn('in_products', array( - 'header_css_class' => 'a-center', - 'type' => 'checkbox', - 'name' => 'in_products', - 'values' => $this->_getSelectedProducts(), - 'align' => 'center', - 'index' => 'entity_id' + 'header_css_class' => 'a-center', + 'type' => 'checkbox', + 'name' => 'in_products', + 'values' => $this->_getSelectedProducts(), + 'align' => 'center', + 'index' => 'entity_id' )); } $this->addColumn('entity_id', array( 'header' => Mage::helper('catalog')->__('ID'), 'sortable' => true, - 'width' => '60px', + 'width' => 60, 'index' => 'entity_id' )); + $this->addColumn('name', array( 'header' => Mage::helper('catalog')->__('Name'), 'index' => 'name' )); - $this->addColumn('type', - array( - 'header'=> Mage::helper('catalog')->__('Type'), - 'width' => '100px', - 'index' => 'type_id', - 'type' => 'options', - 'options' => Mage::getSingleton('catalog/product_type')->getOptionArray(), + $this->addColumn('type', array( + 'header' => Mage::helper('catalog')->__('Type'), + 'width' => 100, + 'index' => 'type_id', + 'type' => 'options', + 'options' => Mage::getSingleton('catalog/product_type')->getOptionArray(), )); $sets = Mage::getResourceModel('eav/entity_attribute_set_collection') @@ -145,73 +163,89 @@ protected function _prepareColumns() ->load() ->toOptionHash(); - $this->addColumn('set_name', - array( - 'header'=> Mage::helper('catalog')->__('Attrib. Set Name'), - 'width' => '130px', - 'index' => 'attribute_set_id', - 'type' => 'options', - 'options' => $sets, + $this->addColumn('set_name', array( + 'header' => Mage::helper('catalog')->__('Attrib. Set Name'), + 'width' => 130, + 'index' => 'attribute_set_id', + 'type' => 'options', + 'options' => $sets, )); - $this->addColumn('status', - array( - 'header'=> Mage::helper('catalog')->__('Status'), - 'width' => '90px', - 'index' => 'status', - 'type' => 'options', - 'options' => Mage::getSingleton('catalog/product_status')->getOptionArray(), + $this->addColumn('status', array( + 'header' => Mage::helper('catalog')->__('Status'), + 'width' => 90, + 'index' => 'status', + 'type' => 'options', + 'options' => Mage::getSingleton('catalog/product_status')->getOptionArray(), )); - $this->addColumn('visibility', - array( - 'header'=> Mage::helper('catalog')->__('Visibility'), - 'width' => '90px', - 'index' => 'visibility', - 'type' => 'options', - 'options' => Mage::getSingleton('catalog/product_visibility')->getOptionArray(), + $this->addColumn('visibility', array( + 'header' => Mage::helper('catalog')->__('Visibility'), + 'width' => 90, + 'index' => 'visibility', + 'type' => 'options', + 'options' => Mage::getSingleton('catalog/product_visibility')->getOptionArray(), )); $this->addColumn('sku', array( 'header' => Mage::helper('catalog')->__('SKU'), - 'width' => '80px', + 'width' => 80, 'index' => 'sku' )); + $this->addColumn('price', array( - 'header' => Mage::helper('catalog')->__('Price'), - 'type' => 'currency', + 'header' => Mage::helper('catalog')->__('Price'), + 'type' => 'currency', 'currency_code' => (string) Mage::getStoreConfig(Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE), - 'index' => 'price' + 'index' => 'price' )); $this->addColumn('position', array( - 'header' => Mage::helper('catalog')->__('Position'), - 'name' => 'position', - 'width' => '60px', - 'type' => 'number', - 'validate_class' => 'validate-number', - 'index' => 'position', - 'editable' => !$this->isReadonly(), - 'edit_only' => !$this->_getProduct()->getId() + 'header' => Mage::helper('catalog')->__('Position'), + 'name' => 'position', + 'width' => 60, + 'type' => 'number', + 'validate_class' => 'validate-number', + 'index' => 'position', + 'editable' => !$this->isReadonly(), + 'edit_only' => !$this->_getProduct()->getId() )); - - return parent::_prepareColumns(); } + /** + * Rerieve grid URL + * + * @return string + */ public function getGridUrl() { - return $this->_getData('grid_url') ? $this->_getData('grid_url') : $this->getUrl('*/*/crosssell', array('_current'=>true)); + return $this->_getData('grid_url') ? $this->_getData('grid_url') : $this->getUrl('*/*/crosssellGrid', array('_current'=>true)); } + /** + * Retrieve selected crosssell products + * + * @return array + */ protected function _getSelectedProducts() { - $products = $this->getRequest()->getPost('products', null); + $products = $this->getProductsCrossSell(); if (!is_array($products)) { - $products = $this->_getProduct()->getCrossSellProductIds(); + $products = $this->getSelectedCrossSellProducts(); } return $products; } + + /** + * Retrieve crosssell products + * + * @return array + */ + public function getSelectedCrossSellProducts() + { + return Mage::registry('current_product')->getCrossSellProductIds(); + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Options/Option.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Options/Option.php index bdf569c05f..63c23c3bc8 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Options/Option.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Options/Option.php @@ -38,9 +38,6 @@ class Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Options_Option extends Mage_ protected $_productInstance; - protected $_name = 'product[options]'; - protected $_id = 'product_option'; - protected $_values; protected $_itemCount = 1; @@ -86,14 +83,24 @@ public function setProduct($product) return $this; } + /** + * Retrieve options field name prefix + * + * @return string + */ public function getFieldName() { - return $this->_name; + return 'product[options]'; } + /** + * Retrieve options field id prefix + * + * @return string + */ public function getFieldId() { - return $this->_id; + return 'product_option'; } /** diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Options/Type/Abstract.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Options/Type/Abstract.php index 3fb72af344..2d38dffe35 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Options/Type/Abstract.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Options/Type/Abstract.php @@ -44,10 +44,12 @@ protected function _prepareLayout() 'id' => 'product_option_{{option_id}}_price_type', 'class' => 'select product-option-price-type' )) - ->setName('product[options][{{option_id}}][price_type]') - ->setOptions(Mage::getSingleton('adminhtml/system_config_source_product_options_price')->toOptionArray()) ); + $this->getChild('option_price_type')->setName('product[options][{{option_id}}][price_type]') + ->setOptions(Mage::getSingleton('adminhtml/system_config_source_product_options_price') + ->toOptionArray()); + return parent::_prepareLayout(); } diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Price/Tier.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Price/Tier.php index 10f9199c1d..654f4d8c54 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Price/Tier.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Price/Tier.php @@ -25,154 +25,273 @@ */ /** - * Adminhtml tier pricing item renderer + * Adminhtml tier price item renderer * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Price_Tier extends Mage_Adminhtml_Block_Widget implements Varien_Data_Form_Element_Renderer_Interface +class Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Price_Tier + extends Mage_Adminhtml_Block_Widget + implements Varien_Data_Form_Element_Renderer_Interface { + /** + * Form element instance + * + * @var Varien_Data_Form_Element + */ + protected $_element; + + /** + * Customer Groups cache + * + * @var array + */ + protected $_customerGroups; - protected $_element = null; - protected $_customerGroups = null; - protected $_websites = null; + /** + * Websites cache + * + * @var array + */ + protected $_websites; + /** + * Define tier price template file + * + */ public function __construct() { $this->setTemplate('catalog/product/edit/price/tier.phtml'); } + /** + * Retrieve current edit product instance + * + * @return Mage_Catalog_Model_Product + */ public function getProduct() { return Mage::registry('product'); } + /** + * Render HTML + * + * @param Varien_Data_Form_Element_Abstract $element + * @return string + */ public function render(Varien_Data_Form_Element_Abstract $element) { $this->setElement($element); return $this->toHtml(); } + /** + * Set form element instance + * + * @param Varien_Data_Form_Element_Abstract $element + * @return Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Price_Tier + */ public function setElement(Varien_Data_Form_Element_Abstract $element) { $this->_element = $element; return $this; } + /** + * Retrieve form element instance + * + * @return Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Price_Tier + */ public function getElement() { return $this->_element; } + /** + * Prepare Tier Price values + * + * @return array + */ public function getValues() { - $values =array(); - $data = $this->getElement()->getValue(); + $values = array(); + $data = $this->getElement()->getValue(); if (is_array($data)) { usort($data, array($this, '_sortTierPrices')); $values = $data; } + + foreach ($values as &$v) { + $v['readonly'] = $v['website_id'] == 0 && $this->isShowWebsiteColumn() && !$this->isAllowChangeWebsite(); + } + return $values; } + /** + * Sort tier price values callback method + * + * @param array $a + * @param array $b + * @return int + */ protected function _sortTierPrices($a, $b) { - if ($a['website_id']!=$b['website_id']) { - return $a['website_id']<$b['website_id'] ? -1 : 1; + if ($a['website_id'] != $b['website_id']) { + return $a['website_id'] < $b['website_id'] ? -1 : 1; } - if ($a['cust_group']!=$b['cust_group']) { - return $this->getCustomerGroups($a['cust_group'])<$this->getCustomerGroups($b['cust_group']) ? -1 : 1; + if ($a['cust_group'] != $b['cust_group']) { + return $this->getCustomerGroups($a['cust_group']) < $this->getCustomerGroups($b['cust_group']) ? -1 : 1; } - if ($a['price_qty']!=$b['price_qty']) { - return $a['price_qty']<$b['price_qty'] ? -1 : 1; + if ($a['price_qty'] != $b['price_qty']) { + return $a['price_qty'] < $b['price_qty'] ? -1 : 1; } + return 0; } - public function getCustomerGroups($groupId=null) + /** + * Retrieve allowed customer groups + * + * @param int $groupId return name by customer group id + * @return array|string + */ + public function getCustomerGroups($groupId = null) { - if (!$this->_customerGroups) { - $collection = Mage::getModel('customer/group')->getCollection() - ->load(); + if (is_null($this->_customerGroups)) { + $collection = Mage::getModel('customer/group')->getCollection(); $this->_customerGroups = array( - Mage_Customer_Model_Group::CUST_GROUP_ALL => Mage::helper('catalog')->__('ALL GROUPS'), + Mage_Customer_Model_Group::CUST_GROUP_ALL => Mage::helper('catalog')->__('ALL GROUPS') ); - foreach ($collection->getIterator() as $item) { + + foreach ($collection as $item) { + /* @var $item Mage_Customer_Model_Group */ $this->_customerGroups[$item->getId()] = $item->getCustomerGroupCode(); } } - return is_null($groupId) ? $this->_customerGroups : - (isset($this->_customerGroups[$groupId]) ? $this->_customerGroups[$groupId] : null); + + if (!is_null($groupId)) { + return isset($this->_customerGroups[$groupId]) ? $this->_customerGroups[$groupId] : null; + } + + return $this->_customerGroups; } + /** + * Retrieve count of websites + * + * @return int + */ public function getWebsiteCount() { return count($this->getWebsites()); } + /** + * Show Website column and switcher for tier price table + * + * @return bool + */ public function isMultiWebsites() { return !Mage::app()->isSingleStoreMode(); } + /** + * Retrieve allowed for edit websites + * + * @return array + */ public function getWebsites() { if (!is_null($this->_websites)) { return $this->_websites; } - $websites = array(); - $websites[0] = array( - 'name' => $this->__('All Websites'), - 'currency' => Mage::app()->getBaseCurrencyCode() + + $this->_websites = array( + 0 => array( + 'name' => Mage::helper('catalog')->__('All Websites'), + 'currency' => Mage::app()->getBaseCurrencyCode() + ) ); - if (Mage::app()->isSingleStoreMode() || $this->getElement()->getEntityAttribute()->isScopeGlobal()) { - return $websites; - } - elseif ($storeId = $this->getProduct()->getStoreId()) { - $website = Mage::app()->getStore($storeId)->getWebsite(); - $websites[$website->getId()] = array( + + if (!$this->isScopeGlobal() && $this->getProduct()->getStoreId()) { + /* @var $website Mage_Core_Model_Website */ + $website = Mage::app()->getStore($this->getProduct()->getStoreId())->getWebsite(); + + $this->_websites[$website->getId()] = array( 'name' => $website->getName(), - 'currency' => $website->getConfig(Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE), - ); - } - else { - $websites[0] = array( - 'name' => $this->__('All Websites'), - 'currency' => Mage::app()->getBaseCurrencyCode() + 'currency' => $website->getBaseCurrencyCode() ); - foreach (Mage::app()->getWebsites() as $website) { - if (!in_array($website->getId(), $this->getProduct()->getWebsiteIds())) { + } else if (!$this->isScopeGlobal()) { + $websites = Mage::app()->getWebsites(false); + $productWebsiteIds = $this->getProduct()->getWebsiteIds(); + foreach ($websites as $website) { + /* @var $website Mage_Core_Model_Website */ + if (!in_array($website->getId(), $productWebsiteIds)) { continue; } - $websites[$website->getId()] = array( + $this->_websites[$website->getId()] = array( 'name' => $website->getName(), - 'currency' => $website->getConfig(Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE), + 'currency' => $website->getBaseCurrencyCode() ); } } - $this->_websites = $websites; + return $this->_websites; } + /** + * Retrieve default value for customer group + * + * @return int + */ public function getDefaultCustomerGroup() { return Mage_Customer_Model_Group::CUST_GROUP_ALL; } + /** + * Retrieve default value for website + * + * @return int + */ + public function getDefaultWebsite() + { + if ($this->isShowWebsiteColumn() && !$this->isAllowChangeWebsite()) { + return Mage::app()->getStore($this->getProduct()->getStoreId())->getWebsiteId(); + } + return 0; + } + + /** + * Prepare global layout + * Add "Add tier" button to layout + * + * @return Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Price_Tier + */ protected function _prepareLayout() { - $this->setChild('add_button', - $this->getLayout()->createBlock('adminhtml/widget_button') - ->setData(array( - 'label' => Mage::helper('catalog')->__('Add Tier'), - 'onclick' => 'tierPriceControl.addItem()', - 'class' => 'add' - ))); + $button = $this->getLayout()->createBlock('adminhtml/widget_button') + ->setData(array( + 'label' => Mage::helper('catalog')->__('Add Tier'), + 'onclick' => 'return tierPriceControl.addItem()', + 'class' => 'add' + )); + $button->setName('add_tier_price_item_button'); + + $this->setChild('add_button', $button); return parent::_prepareLayout(); } + /** + * Retrieve Add Tier Price Item button HTML + * + * @return string + */ public function getAddButtonHtml() { return $this->getChildHtml('add_button'); @@ -193,4 +312,52 @@ public function getPriceColumnHeader($default) return $default; } } -} \ No newline at end of file + + /** + * Retrieve Tier Price entity attribute + * + * @return Mage_Catalog_Model_Resource_Eav_Attribute + */ + public function getAttribute() + { + return $this->getElement()->getEntityAttribute(); + } + + /** + * Check tier price attribute scope is global + * + * @return bool + */ + public function isScopeGlobal() + { + return $this->getAttribute()->isScopeGlobal(); + } + + /** + * Show tier prices grid website column + * + * @return bool + */ + public function isShowWebsiteColumn() + { + if ($this->isScopeGlobal()) { + return false; + } else if (Mage::app()->isSingleStoreMode()) { + return false; + } + return true; + } + + /** + * Check is allow change website value for combination + * + * @return bool + */ + public function isAllowChangeWebsite() + { + if (!$this->isShowWebsiteColumn() || $this->getProduct()->getStoreId()) { + return false; + } + return true; + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Related.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Related.php index 5f957300ea..50d351ad84 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Related.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Related.php @@ -33,6 +33,10 @@ */ class Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Related extends Mage_Adminhtml_Block_Widget_Grid { + /** + * Set grid params + * + */ public function __construct() { parent::__construct(); @@ -54,6 +58,12 @@ protected function _getProduct() return Mage::registry('current_product'); } + /** + * Add filter + * + * @param object $column + * @return Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Related + */ protected function _addColumnFilterToCollection($column) { // Set custom filter for in product flag @@ -64,19 +74,22 @@ protected function _addColumnFilterToCollection($column) } if ($column->getFilter()->getValue()) { $this->getCollection()->addFieldToFilter('entity_id', array('in'=>$productIds)); - } - else { + } else { if($productIds) { $this->getCollection()->addFieldToFilter('entity_id', array('nin'=>$productIds)); } } - } - else { + } else { parent::_addColumnFilterToCollection($column); } return $this; } + /** + * Prepare collection + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ protected function _prepareCollection() { $collection = Mage::getModel('catalog/product_link')->useRelatedLinks() @@ -106,24 +119,28 @@ public function isReadonly() return $this->_getProduct()->getRelatedReadonly(); } - + /** + * Add columns to grid + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ protected function _prepareColumns() { if (!$this->isReadonly()) { $this->addColumn('in_products', array( - 'header_css_class' => 'a-center', - 'type' => 'checkbox', - 'name' => 'in_products', - 'values' => $this->_getSelectedProducts(), - 'align' => 'center', - 'index' => 'entity_id' + 'header_css_class' => 'a-center', + 'type' => 'checkbox', + 'name' => 'in_products', + 'values' => $this->_getSelectedProducts(), + 'align' => 'center', + 'index' => 'entity_id' )); } $this->addColumn('entity_id', array( 'header' => Mage::helper('catalog')->__('ID'), 'sortable' => true, - 'width' => '60px', + 'width' => 60, 'index' => 'entity_id' )); $this->addColumn('name', array( @@ -131,13 +148,12 @@ protected function _prepareColumns() 'index' => 'name' )); - $this->addColumn('type', - array( - 'header'=> Mage::helper('catalog')->__('Type'), - 'width' => '100px', - 'index' => 'type_id', - 'type' => 'options', - 'options' => Mage::getSingleton('catalog/product_type')->getOptionArray(), + $this->addColumn('type', array( + 'header' => Mage::helper('catalog')->__('Type'), + 'width' => 100, + 'index' => 'type_id', + 'type' => 'options', + 'options' => Mage::getSingleton('catalog/product_type')->getOptionArray(), )); $sets = Mage::getResourceModel('eav/entity_attribute_set_collection') @@ -145,81 +161,88 @@ protected function _prepareColumns() ->load() ->toOptionHash(); - $this->addColumn('set_name', - array( - 'header'=> Mage::helper('catalog')->__('Attrib. Set Name'), - 'width' => '130px', - 'index' => 'attribute_set_id', - 'type' => 'options', - 'options' => $sets, + $this->addColumn('set_name', array( + 'header' => Mage::helper('catalog')->__('Attrib. Set Name'), + 'width' => 130, + 'index' => 'attribute_set_id', + 'type' => 'options', + 'options' => $sets, )); - $this->addColumn('status', - array( - 'header'=> Mage::helper('catalog')->__('Status'), - 'width' => '90px', - 'index' => 'status', - 'type' => 'options', - 'options' => Mage::getSingleton('catalog/product_status')->getOptionArray(), + $this->addColumn('status', array( + 'header' => Mage::helper('catalog')->__('Status'), + 'width' => 90, + 'index' => 'status', + 'type' => 'options', + 'options' => Mage::getSingleton('catalog/product_status')->getOptionArray(), )); - $this->addColumn('visibility', - array( - 'header'=> Mage::helper('catalog')->__('Visibility'), - 'width' => '90px', - 'index' => 'visibility', - 'type' => 'options', - 'options' => Mage::getSingleton('catalog/product_visibility')->getOptionArray(), + $this->addColumn('visibility', array( + 'header' => Mage::helper('catalog')->__('Visibility'), + 'width' => 90, + 'index' => 'visibility', + 'type' => 'options', + 'options' => Mage::getSingleton('catalog/product_visibility')->getOptionArray(), )); $this->addColumn('sku', array( 'header' => Mage::helper('catalog')->__('SKU'), - 'width' => '80px', + 'width' => 80, 'index' => 'sku' )); + $this->addColumn('price', array( - 'header' => Mage::helper('catalog')->__('Price'), - 'type' => 'currency', + 'header' => Mage::helper('catalog')->__('Price'), + 'type' => 'currency', 'currency_code' => (string) Mage::getStoreConfig(Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE), - 'index' => 'price' + 'index' => 'price' )); - /*$this->addColumn('qty', array( - 'header' => Mage::helper('catalog')->__('Default Qty'), - 'name' => 'qty', - 'align' => 'center', - 'type' => 'number', - 'validate_class' => 'validate-number', - 'index' => 'qty', - 'width' => '60px', - 'editable' => true - ));*/ - $this->addColumn('position', array( - 'header' => Mage::helper('catalog')->__('Position'), - 'name' => 'position', - 'type' => 'number', - 'validate_class' => 'validate-number', - 'index' => 'position', - 'width' => '60px', - 'editable' => !$this->isReadonly(), - 'edit_only' => !$this->_getProduct()->getId() + 'header' => Mage::helper('catalog')->__('Position'), + 'name' => 'position', + 'type' => 'number', + 'validate_class' => 'validate-number', + 'index' => 'position', + 'width' => 60, + 'editable' => !$this->isReadonly(), + 'edit_only' => !$this->_getProduct()->getId() )); return parent::_prepareColumns(); } + /** + * Rerieve grid URL + * + * @return string + */ public function getGridUrl() { - return $this->getData('grid_url') ? $this->getData('grid_url') : $this->getUrl('*/*/related', array('_current'=>true)); + return $this->getData('grid_url') ? $this->getData('grid_url') : $this->getUrl('*/*/relatedGrid', array('_current'=>true)); } + /** + * Retrieve selected related products + * + * @return array + */ protected function _getSelectedProducts() { - $products = $this->getRequest()->getPost('products', null); + $products = $this->getProductsRelated(); if (!is_array($products)) { - $products = $this->_getProduct()->getRelatedProductIds(); + $products = $this->getSelectedRelatedProducts(); } return $products; } + + /** + * Retrieve related products + * + * @return array + */ + public function getSelectedRelatedProducts() + { + return Mage::registry('current_product')->getRelatedProductIds(); + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Config/Grid/Filter/Inventory.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Config/Grid/Filter/Inventory.php index 732a343b8e..a948597e47 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Config/Grid/Filter/Inventory.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Config/Grid/Filter/Inventory.php @@ -40,7 +40,7 @@ protected function _getOptions() return array( array( 'value' => '', - 'label' => Mage::helper('catalog')->__('') + 'label' => '' ), array( 'value' => 1, diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Group.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Group.php index 551b07c022..9d2bd64756 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Group.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Group.php @@ -164,18 +164,34 @@ protected function _prepareColumns() public function getGridUrl() { - return $this->_getData('grid_url') ? $this->_getData('grid_url') : $this->getUrl('*/*/superGroup', array('_current'=>true)); + return $this->_getData('grid_url') ? $this->_getData('grid_url') : $this->getUrl('*/*/superGroupGridOnly', array('_current'=>true)); } + /** + * Retrieve selected grouped products + * + * @return array + */ protected function _getSelectedProducts() { - $products = $this->getRequest()->getPost('products', null); + $products = $this->getProductsGrouped(); if (!is_array($products)) { - $products = $this->_getProduct()->getTypeInstance(true)->getAssociatedProductIds($this->_getProduct()); + $products = $this->getSelectedGroupedProducts(); } return $products; } + /** + * Retrieve grouped products + * + * @return array + */ + public function getSelectedGroupedProducts() + { + return Mage::registry('current_product')->getTypeInstance(true) + ->getAssociatedProductIds(Mage::registry('current_product')); + } + public function getTabLabel() { return Mage::helper('catalog')->__('Associated Products'); diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Tag.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Tag.php index f46288f744..caa5f4df25 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Tag.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Tag.php @@ -45,11 +45,10 @@ public function __construct() protected function _prepareCollection() { - $tagId = Mage::registry('tagId'); $collection = Mage::getModel('tag/tag') ->getResourceCollection() ->addProductFilter($this->getProductId()) - ->addPopularity($tagId); + ->addPopularity(); $this->setCollection($collection); return parent::_prepareCollection(); @@ -80,7 +79,7 @@ protected function _prepareColumns() 'width' => '90px', 'index' => 'status', 'type' => 'options', - 'options' => array( + 'options' => array( Mage_Tag_Model_Tag::STATUS_DISABLED => Mage::helper('catalog')->__('Disabled'), Mage_Tag_Model_Tag::STATUS_PENDING => Mage::helper('catalog')->__('Pending'), Mage_Tag_Model_Tag::STATUS_APPROVED => Mage::helper('catalog')->__('Approved'), @@ -93,17 +92,17 @@ protected function _prepareColumns() protected function getRowUrl($row) { return $this->getUrl('*/tag/edit', array( - 'tag_id' => $row->getId(), - 'product_id' => $this->getProductId(), + 'tag_id' => $row->getId(), + 'product_id' => $this->getProductId(), )); } public function getGridUrl() { return $this->getUrl('*/catalog_product/tagGrid', array( - '_current' => true, - 'id' => $this->getProductId(), - 'product_id' => $this->getProductId(), + '_current' => true, + 'id' => $this->getProductId(), + 'product_id' => $this->getProductId(), )); } } \ No newline at end of file diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Tag/Customer.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Tag/Customer.php index 2688a679dc..648bdb37df 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Tag/Customer.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Tag/Customer.php @@ -49,7 +49,8 @@ protected function _prepareCollection() $collection = Mage::getModel('tag/tag') ->getCustomerCollection() ->addProductFilter($this->getProductId()) - ->addGroupByTag(); + ->addGroupByTag() + ->addDescOrder(); $this->setCollection($collection); return parent::_prepareCollection(); @@ -87,10 +88,7 @@ protected function _prepareColumns() protected function getRowUrl($row) { - return $this->getUrl('*/tag/edit', array( - 'tag_id' => $row->getTagId(), - 'product_id' => $this->getProductId(), - )); + return $this->getUrl('*/customer/edit', array('id' => $row->getCustomerId())); } public function getGridUrl() diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Upsell.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Upsell.php index ba48494b5b..c3c0920305 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Upsell.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Upsell.php @@ -34,6 +34,10 @@ class Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Upsell extends Mage_Adminhtml_Block_Widget_Grid { + /** + * Set grid params + * + */ public function __construct() { parent::__construct(); @@ -55,6 +59,12 @@ protected function _getProduct() return Mage::registry('current_product'); } + /** + * Add filter + * + * @param object $column + * @return Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Upsell + */ protected function _addColumnFilterToCollection($column) { // Set custom filter for in product flag @@ -65,14 +75,12 @@ protected function _addColumnFilterToCollection($column) } if ($column->getFilter()->getValue()) { $this->getCollection()->addFieldToFilter('entity_id', array('in'=>$productIds)); - } - else { + } else { if($productIds) { $this->getCollection()->addFieldToFilter('entity_id', array('nin'=>$productIds)); } } - } - else { + } else { parent::_addColumnFilterToCollection($column); } return $this; @@ -88,6 +96,11 @@ public function isReadonly() return $this->_getProduct()->getUpsellReadonly(); } + /** + * Prepare collection + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ protected function _prepareCollection() { $collection = Mage::getModel('catalog/product_link')->useUpSellLinks() @@ -107,6 +120,11 @@ protected function _prepareCollection() return parent::_prepareCollection(); } + /** + * Add columns to grid + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ protected function _prepareColumns() { if (!$this->_getProduct()->getUpsellReadonly()) { @@ -123,7 +141,7 @@ protected function _prepareColumns() $this->addColumn('entity_id', array( 'header' => Mage::helper('catalog')->__('ID'), 'sortable' => true, - 'width' => '60px', + 'width' => 60, 'index' => 'entity_id' )); $this->addColumn('name', array( @@ -131,13 +149,12 @@ protected function _prepareColumns() 'index' => 'name' )); - $this->addColumn('type', - array( - 'header'=> Mage::helper('catalog')->__('Type'), - 'width' => '100px', - 'index' => 'type_id', - 'type' => 'options', - 'options' => Mage::getSingleton('catalog/product_type')->getOptionArray(), + $this->addColumn('type', array( + 'header' => Mage::helper('catalog')->__('Type'), + 'width' => 100, + 'index' => 'type_id', + 'type' => 'options', + 'options' => Mage::getSingleton('catalog/product_type')->getOptionArray(), )); $sets = Mage::getResourceModel('eav/entity_attribute_set_collection') @@ -145,71 +162,89 @@ protected function _prepareColumns() ->load() ->toOptionHash(); - $this->addColumn('set_name', - array( - 'header'=> Mage::helper('catalog')->__('Attrib. Set Name'), - 'width' => '130px', - 'index' => 'attribute_set_id', - 'type' => 'options', - 'options' => $sets, + $this->addColumn('set_name', array( + 'header' => Mage::helper('catalog')->__('Attrib. Set Name'), + 'width' => 130, + 'index' => 'attribute_set_id', + 'type' => 'options', + 'options' => $sets, )); - $this->addColumn('status', - array( - 'header'=> Mage::helper('catalog')->__('Status'), - 'width' => '90px', - 'index' => 'status', - 'type' => 'options', - 'options' => Mage::getSingleton('catalog/product_status')->getOptionArray(), + $this->addColumn('status', array( + 'header' => Mage::helper('catalog')->__('Status'), + 'width' => 90, + 'index' => 'status', + 'type' => 'options', + 'options' => Mage::getSingleton('catalog/product_status')->getOptionArray(), )); - $this->addColumn('visibility', - array( - 'header'=> Mage::helper('catalog')->__('Visibility'), - 'width' => '90px', - 'index' => 'visibility', - 'type' => 'options', - 'options' => Mage::getSingleton('catalog/product_visibility')->getOptionArray(), + $this->addColumn('visibility', array( + 'header' => Mage::helper('catalog')->__('Visibility'), + 'width' => 90, + 'index' => 'visibility', + 'type' => 'options', + 'options' => Mage::getSingleton('catalog/product_visibility')->getOptionArray(), )); $this->addColumn('sku', array( 'header' => Mage::helper('catalog')->__('SKU'), - 'width' => '80px', + 'width' => 80, 'index' => 'sku' )); + $this->addColumn('price', array( - 'header' => Mage::helper('catalog')->__('Price'), - 'type' => 'currency', + 'header' => Mage::helper('catalog')->__('Price'), + 'type' => 'currency', 'currency_code' => (string) Mage::getStoreConfig(Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE), - 'index' => 'price' + 'index' => 'price' )); $this->addColumn('position', array( - 'header' => Mage::helper('catalog')->__('Position'), - 'name' => 'position', - 'type' => 'number', - 'width' => '60px', - 'validate_class' => 'validate-number', - 'index' => 'position', - 'editable' => !$this->_getProduct()->getUpsellReadonly(), - 'edit_only' => !$this->_getProduct()->getId() + 'header' => Mage::helper('catalog')->__('Position'), + 'name' => 'position', + 'type' => 'number', + 'width' => 60, + 'validate_class' => 'validate-number', + 'index' => 'position', + 'editable' => !$this->_getProduct()->getUpsellReadonly(), + 'edit_only' => !$this->_getProduct()->getId() )); return parent::_prepareColumns(); } + /** + * Rerieve grid URL + * + * @return string + */ public function getGridUrl() { - return $this->_getData('grid_url') ? $this->_getData('grid_url') : $this->getUrl('*/*/upsell', array('_current'=>true)); + return $this->_getData('grid_url') ? $this->_getData('grid_url') : $this->getUrl('*/*/upsellGrid', array('_current'=>true)); } + /** + * Retrieve selected upsell products + * + * @return array + */ protected function _getSelectedProducts() { - $products = $this->getRequest()->getPost('products', null); + $products = $this->getProductsUpsell(); if (!is_array($products)) { - $products = $this->_getProduct()->getUpSellProductIds(); + $products = $this->getSelectedUpsellProducts(); } return $products; } + /** + * Retrieve upsell products + * + * @return array + */ + public function getSelectedUpsellProducts() + { + return Mage::registry('current_product')->getUpSellProductIds(); + } + } diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Widget/Chooser.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Widget/Chooser.php new file mode 100644 index 0000000000..92d0ecc79a --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Widget/Chooser.php @@ -0,0 +1,288 @@ + + */ +class Mage_Adminhtml_Block_Catalog_Product_Widget_Chooser extends Mage_Adminhtml_Block_Widget_Grid +{ + protected $_selectedProducts = array(); + + /** + * Block construction, prepare grid params + * + * @param array $arguments Object data + */ + public function __construct($arguments=array()) + { + parent::__construct($arguments); + $this->setDefaultSort('name'); + $this->setUseAjax(true); + } + + /** + * Prepare chooser element HTML + * + * @param Varien_Data_Form_Element_Abstract $element Form Element + * @return Varien_Data_Form_Element_Abstract + */ + public function prepareElementHtml(Varien_Data_Form_Element_Abstract $element) + { + $uniqId = $element->getId() . md5(microtime()); + $sourceUrl = $this->getUrl('*/catalog_product_widget/chooser', array( + 'uniq_id' => $uniqId, + 'use_massaction' => false, + )); + + $chooser = $this->getLayout()->createBlock('adminhtml/cms_widget_chooser') + ->setElement($element) + ->setTranslationHelper($this->getTranslationHelper()) + ->setConfig($this->getConfig()) + ->setFieldsetId($this->getFieldsetId()) + ->setSourceUrl($sourceUrl) + ->setUniqId($uniqId); + + + if ($element->getValue()) { + $value = explode('/', $element->getValue()); + $productId = isset($value[1]) ? $value[1] : false; + $categoryId = isset($value[2]) ? $value[2] : false; + $label = ''; + if ($categoryId) { + $label = Mage::getSingleton('catalog/category')->load($categoryId)->getName() . ' / ' . $label; + } + if ($productId) { + $label .= Mage::getSingleton('catalog/product')->load($productId)->getName(); + } + $chooser->setLabel($label); + } + + $element->setData('after_element_html', $chooser->toHtml()); + return $element; + } + + /** + * Checkbox Check JS Callback + * + * @return string + */ + public function getCheckboxCheckCallback() + { + if ($this->getUseMassaction()) { + return "function (grid, event) { + $(grid.containerId).fire('product:changed', {}); + }"; + } + } + + /** + * Grid Row JS Callback + * + * @return string + */ + public function getRowClickCallback() + { + if (!$this->getUseMassaction()) { + $chooserJsObject = $this->getId(); + return ' + function (grid, event) { + var trElement = Event.findElement(event, "tr"); + var productId = trElement.down("td").innerHTML; + var productName = trElement.down("td").next().next().innerHTML; + var optionLabel = productName; + var optionValue = "product/" + productId; + if (grid.categoryId) { + optionValue += "/" + grid.categoryId; + } + if (grid.categoryName) { + optionLabel = grid.categoryName + " / " + optionLabel; + } + '.$chooserJsObject.'.setElementValue(optionValue); + '.$chooserJsObject.'.setElementLabel(optionLabel); + '.$chooserJsObject.'.close(); + } + '; + } + } + + /** + * Category Tree node onClick listener js function + * + * @return string + */ + public function getCategoryClickListenerJs() + { + $js = ' + function (node, e) { + {jsObject}.addVarToUrl("category_id", node.attributes.id); + {jsObject}.reload({jsObject}.url); + {jsObject}.categoryId = node.attributes.id; + {jsObject}.categoryName = node.text; + } + '; + $js = str_replace('{jsObject}', $this->getJsObjectName(), $js); + return $js; + } + + /** + * Filter checked/unchecked rows in grid + * + * @param Mage_Adminhtml_Block_Widget_Grid_Column $column + * @return Mage_Adminhtml_Block_Catalog_Product_Widget_Chooser + */ + protected function _addColumnFilterToCollection($column) + { + if ($column->getId() == 'in_products') { + $selected = $this->getSelectedProducts(); + if ($column->getFilter()->getValue()) { + $this->getCollection()->addFieldToFilter('entity_id', array('in'=>$selected)); + } else { + $this->getCollection()->addFieldToFilter('entity_id', array('nin'=>$selected)); + } + } else { + parent::_addColumnFilterToCollection($column); + } + return $this; + } + + /** + * Prepare products collection, defined collection filters (category, product type) + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ + protected function _prepareCollection() + { + /* @var $collection Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection */ + $collection = Mage::getResourceModel('catalog/product_collection') + ->setStoreId(0) + ->addAttributeToSelect('name'); + + if ($categoryId = $this->getCategoryId()) { + $category = Mage::getModel('catalog/category')->load($categoryId); + // $collection->addCategoryFilter($category); + $productIds = $category->getProductsPosition(); + $productIds = array_keys($productIds); + if (empty($productIds)) { + $productIds = 0; + } + $collection->addFieldToFilter('entity_id', array('in' => $productIds)); + } + + if ($productTypeId = $this->getProductTypeId()) { + $collection->addAttributeToFilter('type_id', $productTypeId); + } + + $this->setCollection($collection); + return parent::_prepareCollection(); + } + + /** + * Prepare columns for products grid + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ + protected function _prepareColumns() + { + if ($this->getUseMassaction()) { + $this->addColumn('in_products', array( + 'header_css_class' => 'a-center', + 'type' => 'checkbox', + 'name' => 'in_products', + 'inline_css' => 'checkbox entities', + 'field_name' => 'in_products', + 'values' => $this->getSelectedProducts(), + 'align' => 'center', + 'index' => 'entity_id', + 'use_index' => true, + )); + } + + $this->addColumn('entity_id', array( + 'header' => Mage::helper('catalog')->__('ID'), + 'sortable' => true, + 'width' => '60px', + 'index' => 'entity_id' + )); + $this->addColumn('chooser_sku', array( + 'header' => Mage::helper('catalog')->__('SKU'), + 'name' => 'chooser_sku', + 'width' => '80px', + 'index' => 'sku' + )); + $this->addColumn('chooser_name', array( + 'header' => Mage::helper('catalog')->__('Product Name'), + 'name' => 'chooser_name', + 'index' => 'name' + )); + + return parent::_prepareColumns(); + } + + /** + * Adds additional parameter to URL for loading only products grid + * + * @return string + */ + public function getGridUrl() + { + return $this->getUrl('*/catalog_product_widget/chooser', array( + 'products_grid' => true, + '_current' => true, + 'uniq_id' => $this->getId(), + 'use_massaction' => $this->getUseMassaction(), + 'product_type_id' => $this->getProductTypeId() + )); + } + + /** + * Setter + * + * @param array $selectedProducts + * @return Mage_Adminhtml_Block_Catalog_Product_Widget_Chooser + */ + public function setSelectedProducts($selectedProducts) + { + $this->_selectedProducts = $selectedProducts; + return $this; + } + + /** + * Getter + * + * @return array + */ + public function getSelectedProducts() + { + if ($selectedProducts = $this->getRequest()->getParam('selected_products', null)) { + $this->setSelectedProducts($selectedProducts); + } + return $this->_selectedProducts; + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Search/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Search/Edit/Form.php index a7271a0a5f..da9e6eaf91 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Search/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Search/Edit/Form.php @@ -122,8 +122,8 @@ protected function _prepareForm() . Mage::helper('catalog')->__('(Will make search for the query above return results for this search.)') . '
' . Mage::helper('cms')->__('(eg: domain.com/identifier)') . '
';var_dump($model->getData()); + // 3. Set entered data if was error when we do save $data = Mage::getSingleton('adminhtml/session')->getFormData(true); if (! empty($data)) { @@ -100,14 +99,8 @@ 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')) - ->_addContent($this->getLayout()->createBlock('adminhtml/cms_page_edit')->setData('action', $this->getUrl('*/cms_page/save'))) - ->_addLeft($this->getLayout()->createBlock('adminhtml/cms_page_edit_tabs')); + ->_addBreadcrumb($id ? Mage::helper('cms')->__('Edit Page') : Mage::helper('cms')->__('New Page'), $id ? Mage::helper('cms')->__('Edit Page') : Mage::helper('cms')->__('New Page')); - if (Mage::app()->getConfig()->getModuleConfig('Mage_GoogleOptimizer')->is('active', true) - && Mage::helper('googleoptimizer')->isOptimizerActiveForCms()) { - $this->_addJs($this->getLayout()->createBlock('googleoptimizer/js')->setTemplate('googleoptimizer/js.phtml')); - } $this->renderLayout(); } @@ -118,7 +111,8 @@ public function saveAction() { // check if data sent if ($data = $this->getRequest()->getPost()) { - // init model and set data + $data = $this->_filterPostData($data); + //init model and set data $model = Mage::getModel('cms/page'); // if ($id = $this->getRequest()->getParam('page_id')) { @@ -153,15 +147,24 @@ public function saveAction() $this->_redirect('*/*/'); return; - } catch (Exception $e) { - // display error message - Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); - // save data in session - Mage::getSingleton('adminhtml/session')->setFormData($data); - // redirect to edit form - $this->_redirect('*/*/edit', array('page_id' => $this->getRequest()->getParam('page_id'))); - return; + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } + catch (Exception $e) { + $this->_getSession()->addException($e, Mage::helper('cms')->__('Error while saving Page. Please try again later.')); +// $this->_getSession()->setFormData($data); +// // display error message +// Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); +// // save data in session +// Mage::getSingleton('adminhtml/session')->setFormData($data); +// // redirect to edit form +// $this->_redirect('*/*/edit', array('page_id' => $this->getRequest()->getParam('page_id'))); +// return; } + + $this->_getSession()->setFormData($data); + $this->_redirect('*/*/edit', array('page_id' => $this->getRequest()->getParam('page_id'))); + return; } $this->_redirect('*/*/'); } @@ -209,6 +212,46 @@ public function deleteAction() */ protected function _isAllowed() { - return Mage::getSingleton('admin/session')->isAllowed('cms/page'); + switch ($this->getRequest()->getActionName()) { + case 'new': + case 'save': + return Mage::getSingleton('admin/session')->isAllowed('cms/page/save'); + break; + case 'delete': + return Mage::getSingleton('admin/session')->isAllowed('cms/page/delete'); + break; + default: + return Mage::getSingleton('admin/session')->isAllowed('cms/page'); + break; + } + } + + /** + * Filtering posted data. Converting localized data if needed + * + * @param array + * @return array + */ + protected function _filterPostData($data) + { + $filterInput = new Zend_Filter_LocalizedToNormalized(array( + 'date_format' => Mage::app()->getLocale()->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT) + )); + + $filterInternal = new Zend_Filter_NormalizedToLocalized(array( + 'date_format' => Varien_Date::DATE_INTERNAL_FORMAT + )); + + if (isset($data['custom_theme_from']) && $data['custom_theme_from']) { + $data['custom_theme_from'] = $filterInput->filter($data['custom_theme_from']); + $data['custom_theme_from'] = $filterInternal->filter($data['custom_theme_from']); + } + + if (isset($data['custom_theme_to']) && $data['custom_theme_to']) { + $data['custom_theme_to'] = $filterInput->filter($data['custom_theme_to']); + $data['custom_theme_to'] = $filterInternal->filter($data['custom_theme_to']); + } + + return $data; } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Adminhtml/controllers/Cms/WidgetController.php b/app/code/core/Mage/Adminhtml/controllers/Cms/WidgetController.php new file mode 100644 index 0000000000..52a77d8a46 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/controllers/Cms/WidgetController.php @@ -0,0 +1,110 @@ + + */ +class Mage_Adminhtml_Cms_WidgetController extends Mage_Adminhtml_Controller_Action +{ + /** + * Wisywyg widget plugin main page + */ + public function indexAction() + { + $this->loadLayout('popup'); + $this->getLayout()->getBlock('root')->addBodyClass('page-popup'); + + $header = $this->getLayout()->getBlock('head'); + $header->setCanLoadExtJs(true); + + // set extra params to widgets insertion form + $this->getLayout()->getBlock('wysiwyg_widget')->addData(array( + 'skip_context_widgets' => $this->getRequest()->getParam('skip_context_widgets'), + 'skip_widgets' => $this->getRequest()->getParam('skip_widgets'), + )); + + // Include WYSIWYG popup helper if WYSIWYG instance exists + if (!$this->getRequest()->getParam('no_wysiwyg')) { + $header->addJs('tiny_mce/tiny_mce_popup.js'); + } + + // Add extra JS files required for widgets + $config = Mage::getSingleton('cms/widget')->getXmlConfig(); + $widgets = $config->getNode('widgets'); + foreach ($widgets->children() as $widget) { + if ($widget->js) { + foreach (explode(',', (string)$widget->js) as $js) { + $header->addJs($js); + } + } + } + + $this->renderLayout(); + } + + /** + * Ajax responder for loading plugin options form + */ + public function loadOptionsAction() + { + try { + $this->loadLayout('empty'); + $optionsBlock = $this->getLayout()->getBlock('wysiwyg_widget.options'); + if ($optionsBlock && $paramsJson = $this->getRequest()->getParam('widget')) { + $request = Mage::helper('core')->jsonDecode($paramsJson); + if (is_array($request)) { + if (isset($request['widget_type'])) { + $optionsBlock->setWidgetType($request['widget_type']); + } + if (isset($request['values'])) { + $optionsBlock->setWidgetValues($request['values']); + } + } + $this->renderLayout(); + } + } catch (Exception $e) { + $result = array('error' => true, 'message' => $e->getMessage()); + $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result)); + } + } + + /** + * Format widget pseudo-code for inserting into wysiwyg editor + */ + public function buildWidgetAction() + { + $type = $this->getRequest()->getPost('widget_type'); + $params = $this->getRequest()->getPost('parameters', array()); + $asIs = $this->getRequest()->getPost('as_is'); + $html = Mage::getSingleton('cms/widget')->getWidgetDeclaration($type, $params, $asIs); + $this->getResponse()->setBody($html); + } +} diff --git a/app/code/core/Mage/Adminhtml/controllers/Cms/Wysiwyg/ImagesController.php b/app/code/core/Mage/Adminhtml/controllers/Cms/Wysiwyg/ImagesController.php new file mode 100644 index 0000000000..c7d6eb30c9 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/controllers/Cms/Wysiwyg/ImagesController.php @@ -0,0 +1,187 @@ + + */ +class Mage_Adminhtml_Cms_Wysiwyg_ImagesController extends Mage_Adminhtml_Controller_Action +{ + /** + * Init storage + * + * @return Mage_Adminhtml_Cms_Page_Wysiwyg_ImagesController + */ + protected function _initAction() + { + $this->getStorage(); + return $this; + } + + public function indexAction() + { + try { + Mage::helper('cms/wysiwyg_images')->getCurrentPath(); + } catch (Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } + $this->_initAction(); + $this->loadLayout('popup'); + $this->getLayout()->getBlock('root')->addBodyClass('page-popup'); + $this->getLayout()->getBlock('head')->setCanLoadExtJs(true); + $this->renderLayout(); + } + + public function treeJsonAction() + { + try { + $this->_initAction()->_saveSessionCurrentPath(); + $this->getResponse()->setBody( + $this->getLayout()->createBlock('adminhtml/cms_wysiwyg_images_tree') + ->getTreeJson() + ); + } catch (Exception $e) { + $this->getResponse()->setBody(Mage::helper('core')->jsonEncode(array())); + } + } + + public function contentsAction() + { + try { + $this->_initAction()->_saveSessionCurrentPath(); + $this->loadLayout('empty'); + $this->renderLayout(); + } catch (Exception $e) { + $result = array('error' => true, 'message' => $e->getMessage()); + $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result)); + } + } + + public function newFolderAction() + { + try { + $this->_initAction(); + $name = $this->getRequest()->getPost('name'); + $path = $this->getStorage()->getSession()->getCurrentPath(); + $result = $this->getStorage()->createDirectory($name, $path); + } catch (Exception $e) { + $result = array('error' => true, 'message' => $e->getMessage()); + } + $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result)); + } + + public function deleteFolderAction() + { + try { + $path = $this->getStorage()->getSession()->getCurrentPath(); + $this->getStorage()->deleteDirectory($path); + } catch (Exception $e) { + $result = array('error' => true, 'message' => $e->getMessage()); + $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result)); + } + } + + public function deleteFilesAction() + { + $files = Mage::helper('core')->jsonDecode($this->getRequest()->getParam('files')); + try { + $io = new Varien_Io_File(); + foreach ($files as $file) { + $file = Mage::helper('core')->urlDecode($file); + $io->rm($this->getStorage()->getSession()->getCurrentPath(). DS . $file); + } + } catch (Exception $e) { + $result = array('error' => true, 'message' => $e->getMessage()); + $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result)); + } + } + + /** + * Files upload processing + */ + public function uploadAction() + { + try { + $result = array(); + $this->_initAction(); + $targetPath = $this->getStorage()->getSession()->getCurrentPath(); + $result = $this->getStorage()->uploadFile($targetPath, $this->getRequest()->getParam('type')); + } catch (Exception $e) { + $result = array('error' => $e->getMessage(), 'errorcode' => $e->getCode()); + } + $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result)); + + } + + /** + * Fire when select image + */ + public function onInsertAction() + { + $filename = $this->getRequest()->getParam('filename'); + $filename = Mage::helper('core')->urlDecode($filename); + $fileurl = Mage::helper('cms/wysiwyg_images')->getCurrentUrl() . $filename; + $mediaPath = str_replace(Mage::getBaseUrl('media'), '', $fileurl); + $directive = sprintf('{{media url="%s"}}', $mediaPath); + if ($this->getRequest()->getParam('as_is')) { + $directive = sprintf('', $directive); + } else { + $directive = $this->getUrl('*/cms_wysiwyg/directive', array('directive' => Mage::helper('core')->urlEncode($directive))); + } + $this->getResponse()->setBody($directive); + } + + /** + * Register storage model and return it + * + * @return Mage_Cms_Model_Page_Wysiwyg_Images_Storage + */ + public function getStorage() + { + if (!Mage::registry('storage')) { + $storage = Mage::getModel('cms/wysiwyg_images_storage'); + Mage::register('storage', $storage); + } + return Mage::registry('storage'); + } + + /** + * Save current path in session + * + * @return Mage_Adminhtml_Cms_Page_Wysiwyg_ImagesController + */ + protected function _saveSessionCurrentPath() + { + $this->getStorage() + ->getSession() + ->setCurrentPath(Mage::helper('cms/wysiwyg_images')->getCurrentPath()); + return $this; + } +} diff --git a/app/code/core/Mage/Adminhtml/controllers/Cms/WysiwygController.php b/app/code/core/Mage/Adminhtml/controllers/Cms/WysiwygController.php new file mode 100644 index 0000000000..98e6845faf --- /dev/null +++ b/app/code/core/Mage/Adminhtml/controllers/Cms/WysiwygController.php @@ -0,0 +1,62 @@ + + */ +class Mage_Adminhtml_Cms_WysiwygController extends Mage_Adminhtml_Controller_Action +{ + /** + * Template directives callback + * + * TODO: move this to some model + */ + public function directiveAction() + { + $directive = $this->getRequest()->getParam('directive'); + $directive = Mage::helper('core')->urlDecode($directive); + $url = Mage::getModel('core/email_template_filter')->filter($directive); + try { + $image = Varien_Image_Adapter::factory('GD2'); + $image->open($url); + $image->display(); + } catch (Exception $e) { + $image = imagecreate(100, 100); + $bkgrColor = imagecolorallocate($image,10,10,10); + imagefill($image,0,0,$bkgrColor); + $textColor = imagecolorallocate($image,255,255,255); + imagestring($image, 4, 10, 10, 'Skin image', $textColor); + header('Content-type: image/png'); + imagepng($image); + imagedestroy($image); + } + } +} diff --git a/app/code/core/Mage/Adminhtml/controllers/CustomerController.php b/app/code/core/Mage/Adminhtml/controllers/CustomerController.php index ede6002828..5debd35331 100644 --- a/app/code/core/Mage/Adminhtml/controllers/CustomerController.php +++ b/app/code/core/Mage/Adminhtml/controllers/CustomerController.php @@ -157,6 +157,9 @@ public function saveAction() // Prepare customer saving data if (isset($data['account'])) { + if (isset($data['account']['email'])) { + $data['account']['email'] = trim($data['account']['email']); + } $customer->addData($data['account']); } @@ -190,6 +193,7 @@ public function saveAction() $isNewCustomer = !$customer->getId(); try { if ($customer->getPassword() == 'auto') { + $sendPassToEmail = true; $customer->setPassword($customer->generatePassword()); } @@ -204,14 +208,14 @@ public function saveAction() $customer->save(); // send welcome email - if ($customer->getWebsiteId() && $customer->hasData('sendemail')) { - $store_id = $customer->getStoreId(); + if ($customer->getWebsiteId() && ($customer->hasData('sendemail') || isset($sendPassToEmail))) { + $storeId = $customer->getSendemailStoreId(); if ($isNewCustomer) { - $customer->sendNewAccountEmail('registered', '', $store_id); + $customer->sendNewAccountEmail('registered', '', $storeId); } // confirm not confirmed customer elseif ((!$customer->getConfirmation())) { - $customer->sendNewAccountEmail('confirmed', '', $store_id); + $customer->sendNewAccountEmail('confirmed', '', $storeId); } } @@ -328,7 +332,7 @@ public function wishlistAction() ->delete(); } catch (Exception $e) { - // + Mage::logException($e); } } } diff --git a/app/code/core/Mage/Adminhtml/controllers/DashboardController.php b/app/code/core/Mage/Adminhtml/controllers/DashboardController.php index d69a610ef3..16354e89ea 100644 --- a/app/code/core/Mage/Adminhtml/controllers/DashboardController.php +++ b/app/code/core/Mage/Adminhtml/controllers/DashboardController.php @@ -60,7 +60,7 @@ public function ajaxBlockAction() { $output = ''; $blockTab = $this->getRequest()->getParam('block'); - if (in_array($blockTab, array('tab_orders', 'tab_amounts'))) { + if (in_array($blockTab, array('tab_orders', 'tab_amounts', 'totals'))) { $output = $this->getLayout()->createBlock('adminhtml/dashboard_' . $blockTab)->toHtml(); } $this->getResponse()->setBody($output); diff --git a/app/code/core/Mage/Adminhtml/controllers/Permissions/UserController.php b/app/code/core/Mage/Adminhtml/controllers/Permissions/UserController.php index 7968d8b88a..35bc4c7bb4 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Permissions/UserController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Permissions/UserController.php @@ -85,6 +85,16 @@ public function saveAction() $model = Mage::getModel('admin/user'); $model->setData($data); + /* + * Unsetting new password and password confirmation if they are blank + */ + if ($model->hasNewPassword() && $model->getNewPassword() === '') { + $model->unsNewPassword(); + } + if ($model->hasPasswordConfirmation() && $model->getPasswordConfirmation() === '') { + $model->unsPasswordConfirmation(); + } + $result = $model->validate(); if (is_array($result)) { Mage::getSingleton('adminhtml/session')->setUserData($data); diff --git a/app/code/core/Mage/Adminhtml/controllers/Promo/CatalogController.php b/app/code/core/Mage/Adminhtml/controllers/Promo/CatalogController.php index 41a3479173..3b0a700e3c 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Promo/CatalogController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Promo/CatalogController.php @@ -31,8 +31,7 @@ protected function _initAction() { $this->loadLayout() ->_setActiveMenu('promo/catalog') - ->_addBreadcrumb(Mage::helper('catalogrule')->__('Promotions'), Mage::helper('catalogrule')->__('Promotions')) - ; + ->_addBreadcrumb(Mage::helper('catalogrule')->__('Promotions'), Mage::helper('catalogrule')->__('Promotions')); return $this; } @@ -44,7 +43,6 @@ public function indexAction() $this->_initAction() ->_addBreadcrumb(Mage::helper('catalogrule')->__('Catalog'), Mage::helper('catalogrule')->__('Catalog')) - ->_addContent($this->getLayout()->createBlock('adminhtml/promo_catalog')) ->renderLayout(); } @@ -76,28 +74,22 @@ public function editAction() Mage::register('current_promo_catalog_rule', $model); - $block = $this->getLayout()->createBlock('adminhtml/promo_catalog_edit') - ->setData('action', $this->getUrl('*/promo_catalog/save')); - - $this->_initAction(); - - $this->getLayout()->getBlock('head') - ->setCanLoadExtJs(true) - ->setCanLoadRulesJs(true); + $this->_initAction()->getLayout()->getBlock('promo_catalog_edit') + ->setData('action', $this->getUrl('*/promo_catalog/save')); $this ->_addBreadcrumb($id ? Mage::helper('catalogrule')->__('Edit Rule') : Mage::helper('catalogrule')->__('New Rule'), $id ? Mage::helper('catalogrule')->__('Edit Rule') : Mage::helper('catalogrule')->__('New Rule')) - ->_addContent($block) - ->_addLeft($this->getLayout()->createBlock('adminhtml/promo_catalog_edit_tabs')) ->renderLayout(); } public function saveAction() { - if ($data = $this->getRequest()->getPost()) { + if ($this->getRequest()->getPost()) { try { $model = Mage::getModel('catalogrule/rule'); + Mage::dispatchEvent('adminhtml_controller_catalogrule_prepare_save', array('request' => $this->getRequest())); + $data = $this->getRequest()->getPost(); if ($id = $this->getRequest()->getParam('rule_id')) { $model->load($id); if ($id != $model->getId()) { @@ -115,7 +107,9 @@ public function saveAction() } $model->loadPost($data); + Mage::getSingleton('adminhtml/session')->setPageData($model->getData()); + $model->save(); Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('catalogrule')->__('Rule was successfully saved')); @@ -124,11 +118,18 @@ public function saveAction() $this->_forward('applyRules'); } else { Mage::app()->saveCache(1, 'catalog_rules_dirty'); + if ($this->getRequest()->getParam('back')) { + $this->_redirect('*/*/edit', array('id' => $model->getId())); + return; + } $this->_redirect('*/*/'); } return; + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); } catch (Exception $e) { - Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); + $this->_getSession()->addError(Mage::helper('catalogrule')->__('Error while saving rule data. Please review log and try again.')); + Mage::logException($e); Mage::getSingleton('adminhtml/session')->setPageData($data); $this->_redirect('*/*/edit', array('id' => $this->getRequest()->getParam('rule_id'))); return; @@ -148,9 +149,11 @@ public function deleteAction() Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('catalogrule')->__('Rule was successfully deleted')); $this->_redirect('*/*/'); return; - } - catch (Exception $e) { - Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } catch (Exception $e) { + $this->_getSession()->addError(Mage::helper('catalogrule')->__('Error while deleting rule. Please review log and try again.')); + Mage::logException($e); $this->_redirect('*/*/edit', array('id' => $this->getRequest()->getParam('id'))); return; } @@ -232,8 +235,7 @@ public function newActionHtmlAction() public function applyRulesAction() { try { - $resource = Mage::getResourceSingleton('catalogrule/rule'); - $resource->applyAllRulesForDateRange(); + Mage::getModel('catalogrule/rule')->applyAll(); Mage::app()->removeCache('catalog_rules_dirty'); Mage::getSingleton('adminhtml/session')->addSuccess( Mage::helper('catalogrule')->__('Rules were successfully applied') @@ -244,7 +246,6 @@ public function applyRulesAction() ); throw $e; } - $this->_redirect('*/*'); } @@ -262,4 +263,5 @@ protected function _isAllowed() { return Mage::getSingleton('admin/session')->isAllowed('promo/catalog'); } + } diff --git a/app/code/core/Mage/Adminhtml/controllers/Promo/QuoteController.php b/app/code/core/Mage/Adminhtml/controllers/Promo/QuoteController.php index a159239baa..87b7fe4d36 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Promo/QuoteController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Promo/QuoteController.php @@ -49,7 +49,6 @@ public function indexAction() { $this->_initAction() ->_addBreadcrumb(Mage::helper('salesrule')->__('Catalog'), Mage::helper('salesrule')->__('Catalog')) - ->_addContent($this->getLayout()->createBlock('adminhtml/promo_quote')) ->renderLayout(); } @@ -83,28 +82,22 @@ public function editAction() Mage::register('current_promo_quote_rule', $model); - $block = $this->getLayout()->createBlock('adminhtml/promo_quote_edit') - ->setData('action', $this->getUrl('*/*/save')); - - $this->_initAction(); - $this->getLayout()->getBlock('head') - ->setCanLoadExtJs(true) - ->setCanLoadRulesJs(true); + $this->_initAction()->getLayout()->getBlock('promo_quote_edit') + ->setData('action', $this->getUrl('*/*/save')); $this ->_addBreadcrumb($id ? Mage::helper('salesrule')->__('Edit Rule') : Mage::helper('salesrule')->__('New Rule'), $id ? Mage::helper('salesrule')->__('Edit Rule') : Mage::helper('salesrule')->__('New Rule')) - ->_addContent($block) - ->_addLeft($this->getLayout()->createBlock('adminhtml/promo_quote_edit_tabs')) ->renderLayout(); } public function saveAction() { - $data = $this->getRequest()->getPost(); - if ($data) { + if ($this->getRequest()->getPost()) { try { $model = Mage::getModel('salesrule/rule'); + Mage::dispatchEvent('adminhtml_controller_salesrule_prepare_save', array('request' => $this->getRequest())); + $data = $this->getRequest()->getPost(); $id = $this->getRequest()->getParam('rule_id'); if ($id) { $model->load($id); @@ -130,12 +123,19 @@ public function saveAction() $model->save(); $session->addSuccess(Mage::helper('salesrule')->__('Rule was successfully saved')); $session->setPageData(false); + if ($this->getRequest()->getParam('back')) { + $this->_redirect('*/*/edit', array('id' => $model->getId())); + return; + } $this->_redirect('*/*/'); return; + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); } catch (Exception $e) { - Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); + $this->_getSession()->addError(Mage::helper('catalogrule')->__('Error while saving rule data. Please review log and try again.')); + Mage::logException($e); Mage::getSingleton('adminhtml/session')->setPageData($data); - $this->_redirect('*/*/edit', array('id' => $this->getRequest()->getParam('rule_id'))); + $this->_redirect('*/*/edit', array('id' => $this->getRequest()->getParam('rule_id'))); return; } } @@ -152,9 +152,11 @@ public function deleteAction() Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('salesrule')->__('Rule was successfully deleted')); $this->_redirect('*/*/'); return; - } - catch (Exception $e) { - Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } catch (Exception $e) { + $this->_getSession()->addError(Mage::helper('catalogrule')->__('Error while deleting rule. Please review log and try again.')); + Mage::logException($e); $this->_redirect('*/*/edit', array('id' => $this->getRequest()->getParam('id'))); return; } @@ -214,21 +216,17 @@ public function newActionHtmlAction() public function applyRulesAction() { $this->_initAction(); - $this->renderLayout(); } public function gridAction() { - $this->_initRule(); - $this->getResponse()->setBody( - $this->getLayout()->createBlock('adminhtml/promo_quote_edit_tab_product')->toHtml() - ); + $this->_initRule()->loadLayout()->renderLayout(); + } protected function _isAllowed() { return Mage::getSingleton('admin/session')->isAllowed('promo/quote'); } - } diff --git a/app/code/core/Mage/Adminhtml/controllers/Sales/OrderController.php b/app/code/core/Mage/Adminhtml/controllers/Sales/OrderController.php index 79e8b926b0..035aa1ac3c 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Sales/OrderController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Sales/OrderController.php @@ -116,6 +116,17 @@ public function viewAction() } } + /** + * Notify user + */ + public function emailAction() + { + if ($order = $this->_initOrder()) { + $order->sendNewOrderEmail(); + $this->_getSession()->addSuccess(Mage::helper('sales')->__('Message was successfully sent')); + $this->_redirect('*/sales_order/view', array('order_id' => $order->getId())); + } + } /** * Cancel order */ 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 c3822d0f27..e1a82ca4bb 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/Email/TemplateController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/Email/TemplateController.php @@ -95,13 +95,13 @@ public function saveAction() ->setModifiedAt(Mage::getSingleton('core/date')->gmtDate()); if (!$template->getId()) { - $type = constant(Mage::getConfig()->getModelClassName('core/email_template') . "::TYPE_HTML"); - $template->setTemplateType($type); + //$type = constant(Mage::getConfig()->getModelClassName('core/email_template') . "::TYPE_HTML"); + $template->setTemplateType(Mage_Core_Model_Email_Template::TYPE_HTML); } if($this->getRequest()->getParam('_change_type_flag')) { - $type = constant(Mage::getConfig()->getModelClassName('core/email_template') . "::TYPE_TEXT"); - $template->setTemplateType($type); + //$type = constant(Mage::getConfig()->getModelClassName('core/email_template') . "::TYPE_TEXT"); + $template->setTemplateType(Mage_Core_Model_Email_Template::TYPE_TEXT); } $template->save(); diff --git a/app/code/core/Mage/Adminhtml/controllers/TagController.php b/app/code/core/Mage/Adminhtml/controllers/TagController.php index 40f9fb174a..5805cd6725 100644 --- a/app/code/core/Mage/Adminhtml/controllers/TagController.php +++ b/app/code/core/Mage/Adminhtml/controllers/TagController.php @@ -44,8 +44,42 @@ protected function _initAction() return $this; } + /** + * Prepare tag model for manipulation + * + * @return Mage_Tag_Model_Tag + */ + protected function _initTag() + { + $id = $this->getRequest()->getParam('tag_id'); + $storeId = $this->getRequest()->getParam('store'); + $model = Mage::getModel('tag/tag'); + if ($id) { + $model->load($id); + $model->setStoreId($storeId); + } + Mage::register('current_tag', $model); + + return $model; + } + + /** + * Show grid action + * + */ public function indexAction() { + /** + * setting status parameter for grid filter for non-ajax request + * + */ + if ($this->getRequest()->getParam('pending') && !$this->getRequest()->getParam('isAjax')) { + $this->getRequest()->setParam('filter', base64_encode('status=' . Mage_Tag_Model_Tag::STATUS_PENDING)); + } + elseif (!$this->getRequest()->getParam('isAjax')) { + $this->getRequest()->setParam('filter', ''); + } + $this->_initAction() ->_addBreadcrumb(Mage::helper('adminhtml')->__('All Tags'), Mage::helper('adminhtml')->__('All Tags')) ->_setActiveMenu('catalog/tag/all') @@ -53,40 +87,77 @@ public function indexAction() ->renderLayout(); } + /** + * Action to draw grid loaded by ajax + * + */ + public function ajaxGridAction() + { + $this->loadLayout(); + $this->getResponse()->setBody( + $this->getLayout()->createBlock('adminhtml/tag_tag_grid')->toHtml() + ); + } + + /** + * New tag action + * + */ public function newAction() { $this->_forward('edit'); } + /** + * Edit tag action + * + */ public function editAction() { - $id = $this->getRequest()->getParam('tag_id'); - $model = Mage::getModel('tag/tag'); - - if ($id) { - $model->load($id); + if (0 === (int)$this->getRequest()->getParam('store')) { + $this->_redirect('*/*/*/', array('store' => Mage::app()->getAnyStoreView()->getId(), '_current' => true)); + return; } + $model = $this->_initTag(); + + $model->addSummary($this->getRequest()->getParam('store')); + // set entered data if was error when we do save $data = Mage::getSingleton('adminhtml/session')->getTagData(true); if (! empty($data)) { - $model->setData($data); + $model->addData($data); } Mage::register('tag_tag', $model); - $this->_initAction() - ->_addBreadcrumb($id ? Mage::helper('adminhtml')->__('Edit Tag') : Mage::helper('adminhtml')->__('New Tag'), $id ? Mage::helper('adminhtml')->__('Edit Tag') : Mage::helper('adminhtml')->__('New Tag')) - ->_addContent($this->getLayout()->createBlock('adminhtml/tag_tag_edit')->setData('action', $this->getUrl('*/tag_edit/save'))) - ->renderLayout(); + $this->_initAction()->renderLayout(); } + /** + * Save tag action + * + */ public function saveAction() { - if ($data = $this->getRequest()->getPost()) { - $data['name']=trim($data['name']); - $model = Mage::getModel('tag/tag'); - $model->setData($data); + if ($postData = $this->getRequest()->getPost()) { + if (isset($postData['tag_id'])) { + $data['tag_id'] = $postData['tag_id']; + } + + $data['name'] = trim($postData['tag_name']); + $data['status'] = $postData['tag_status']; + $data['base_popularity'] = (isset($postData['base_popularity'])) ? $postData['base_popularity'] : 0; + $data['store_id'] = $postData['store_id']; + + $model = $this->_initTag(); + $model->addData($data); + + if (isset($postData['tag_assigned_products'])) { + $productIds = Mage::helper('adminhtml/js')->decodeInput($postData['tag_assigned_products']); + $tagRelationModel = Mage::getModel('tag/tag_relation'); + $tagRelationModel->addRelations($model, $productIds); + } switch( $this->getRequest()->getParam('ret') ) { case 'all': @@ -110,12 +181,18 @@ public function saveAction() )); } - // $tag->setStoreId(Mage::app()->getStore()->getId()); try { $model->save(); $model->aggregate(); Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('adminhtml')->__('Tag was successfully saved')); Mage::getSingleton('adminhtml/session')->setTagData(false); + + if ($this->getRequest()->getParam('ret') == 'edit') { + $url = $this->getUrl('*/tag/edit', array( + 'tag_id' => $model->getId() + )); + } + $this->getResponse()->setRedirect($url); return; } catch (Exception $e) { @@ -128,6 +205,10 @@ public function saveAction() $this->getResponse()->setRedirect($url); } + /** + * Delete tag action + * + */ public function deleteAction() { if ($id = $this->getRequest()->getParam('tag_id')) { @@ -155,8 +236,7 @@ public function deleteAction() } try { - $model = Mage::getModel('tag/tag'); - $model->setId($id); + $model = $this->_initTag(); $model->delete(); Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('adminhtml')->__('Tag was successfully deleted')); $this->getResponse()->setRedirect($url); @@ -185,19 +265,38 @@ public function pendingAction() ->renderLayout(); } + /** + * Assigned products (with serializer block) + * + */ + public function assignedAction() + { + $this->_initTag(); + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * Assigned products grid + * + */ + public function assignedGridOnlyAction() + { + $this->_initTag(); + $this->loadLayout(); + $this->renderLayout(); + } + /** * Tagged products * */ public function productAction() { - Mage::register('tagId', $this->getRequest()->getParam('tag_id')); - - $this->_initAction() - ->_addBreadcrumb(Mage::helper('adminhtml')->__('Products'), Mage::helper('adminhtml')->__('Products')) - ->_setActiveMenu('catalog/tag/product') - ->_addContent($this->getLayout()->createBlock('adminhtml/tag_product')) - ->renderLayout(); + $this->_initTag(); + $this->getResponse()->setBody( + $this->getLayout()->createBlock('adminhtml/tag_product_grid')->toHtml() + ); } /** @@ -206,15 +305,16 @@ public function productAction() */ public function customerAction() { - Mage::register('tagId', $this->getRequest()->getParam('tag_id')); - - $this->_initAction() - ->_addBreadcrumb(Mage::helper('adminhtml')->__('Customers'), Mage::helper('adminhtml')->__('Customers')) - ->_setActiveMenu('catalog/tag/customer') - ->_addContent($this->getLayout()->createBlock('adminhtml/tag_customer')) - ->renderLayout(); + $this->_initTag(); + $this->getResponse()->setBody( + $this->getLayout()->createBlock('adminhtml/tag_customer_grid')->toHtml() + ); } + /** + * Massaction for removing tags + * + */ public function massDeleteAction() { $tagIds = $this->getRequest()->getParam('tag'); @@ -237,6 +337,10 @@ public function massDeleteAction() $this->_redirect('*/*/'.$ret); } + /** + * Massaction for changing status of selected tags + * + */ public function massStatusAction() { $tagIds = $this->getRequest()->getParam('tag'); @@ -263,6 +367,10 @@ public function massStatusAction() $this->_redirect('*/*/'.$ret); } + /** + * Check currently called action by permissions for current user + * + */ protected function _isAllowed() { switch ($this->getRequest()->getActionName()) { diff --git a/app/code/core/Mage/Adminhtml/controllers/Tax/RuleController.php b/app/code/core/Mage/Adminhtml/controllers/Tax/RuleController.php index 6e641a7d03..425ea9da77 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Tax/RuleController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Tax/RuleController.php @@ -82,8 +82,13 @@ public function saveAction() $ruleModel->save(); Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('tax')->__('Tax rule was successfully saved')); - $this->_redirect('*/*/'); + if ($this->getRequest()->getParam('back')) { + $this->_redirect('*/*/edit', array('rule' => $ruleModel->getId())); + return; + } + + $this->_redirect('*/*/'); return; } catch (Mage_Core_Exception $e) { diff --git a/app/code/core/Mage/Adminhtml/etc/adminhtml.xml b/app/code/core/Mage/Adminhtml/etc/adminhtml.xml new file mode 100644 index 0000000000..6212260507 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/etc/adminhtml.xml @@ -0,0 +1,276 @@ + + + + + + Dashboard + 10 + adminhtml/dashboard + + + System + 90 + + + + My Account + adminhtml/system_account + 10 + + + Tools + 20 + + + Design + adminhtml/system_design + 30 + + + Import/Export + 40 + + + Profiles + adminhtml/system_convert_gui + + + Advanced Profiles + adminhtml/system_convert_profile + + + + + Manage Currency Rates + adminhtml/system_currency + 50 + + + Transactional Emails + adminhtml/system_email_template + 60 + + + Permissions + 70 + + + Users + adminhtml/permissions_user + + + Roles + adminhtml/permissions_role + + + + + Magento Connect + 80 + + + Magento Connect Manager + adminhtml/extensions_local + + + Package Extensions + adminhtml/extensions_custom + + + + + Cache Management + adminhtml/system_cache + 90 + + + Manage Stores + adminhtml/system_store/ + 100 + + + Configuration + adminhtml/system_config + 110 + + + + + + + + Allow everything + + + Magento Admin + + + Dashboard + 0 + + + System + 90 + + + Permissions + 0 + + + Roles + 10 + + + Users + 20 + + + + + Manage Stores + + + Design + 25 + + + Configuration + 20 + + + General Section + 20 + + + Web Section + 30 + + + Design Section + 40 + + + System Section + 80 + + + Advanced Section + 90 + + + Store Email Addresses Section + 100 + + + Developer Section + 110 + + + Currency Setup Section + 120 + + + Email to a Friend + 140 + + + Advanced Admin Section + 100 + + + + + Manage Currency Rates + 30 + + + Transactional Emails + 40 + + + My Account + 50 + + + Tools + 60 + + + Import/Export + + + Profiles + + + Advanced Profiles + + + + + Cache Management + + + Magento Connect + + + Magento Connect Manager + 0 + + + Package Extensions + 5 + + + + + + + Global Search + 100 + + + + + + + + View entity + + + Edit entity + + + + + + + + + + + diff --git a/app/code/core/Mage/Adminhtml/etc/config.xml b/app/code/core/Mage/Adminhtml/etc/config.xml index 61fd628f23..f85aef09b0 100644 --- a/app/code/core/Mage/Adminhtml/etc/config.xml +++ b/app/code/core/Mage/Adminhtml/etc/config.xml @@ -61,12 +61,10 @@ - singleton adminhtml/observer bindStore - singleton adminhtml/observer massactionPrepareKey @@ -92,7 +90,6 @@ - singleton admin/observer actionPreDispatchAdmin @@ -101,7 +98,6 @@ - singleton adminhtml/observer bindLocale @@ -128,253 +124,6 @@ - - - Dashboard - 10 - adminhtml/dashboard - - - System - 90 - - - - My Account - adminhtml/system_account - 10 - - - Tools - 20 - - - Design - adminhtml/system_design - 30 - - - Import/Export - 40 - - - Profiles - adminhtml/system_convert_gui - - - Advanced Profiles - adminhtml/system_convert_profile - - - - - Manage Currency Rates - adminhtml/system_currency - 50 - - - Transactional Emails - adminhtml/system_email_template - 60 - - - Permissions - 70 - - - Users - adminhtml/permissions_user - - - Roles - adminhtml/permissions_role - - - - - Magento Connect - 80 - - - Magento Connect Manager - adminhtml/extensions_local - - - Package Extensions - adminhtml/extensions_custom - - - - - Cache Management - adminhtml/system_cache - 90 - - - Manage Stores - adminhtml/system_store/ - 100 - - - Configuration - adminhtml/system_config - 110 - - - - - - - - Allow everything - - - Magento Admin - - - Dashboard - 0 - - - System - 90 - - - Permissions - 0 - - - Roles - 10 - - - Users - 20 - - - - - Manage Stores - - - Design - 25 - - - Configuration - 20 - - - General Section - 20 - - - Web Section - 30 - - - Design Section - 40 - - - System Section - 80 - - - Advanced Section - 90 - - - Store Email Addresses Section - 100 - - - Developer Section - 110 - - - Currency Setup Section - 120 - - - Email to a Friend - 140 - - - Advanced Admin Section - 100 - - - - - Manage Currency Rates - 30 - - - Transactional Emails - 40 - - - My Account - 50 - - - Tools - 60 - - - Import/Export - - - Profiles - - - Advanced Profiles - - - - - Cache Management - - - Magento Connect - - - Magento Connect Manager - 0 - - - Package Extensions - 5 - - - - - - - Global Search - 100 - - - - - - - - View entity - - - Edit entity - - - - - - - - - - @@ -386,6 +135,9 @@ customer.xml + + promo.xml + diff --git a/app/code/core/Mage/AmazonPayments/Model/Payment/Asp.php b/app/code/core/Mage/AmazonPayments/Model/Payment/Asp.php index 9ab650d7c0..bb0b365aeb 100644 --- a/app/code/core/Mage/AmazonPayments/Model/Payment/Asp.php +++ b/app/code/core/Mage/AmazonPayments/Model/Payment/Asp.php @@ -34,7 +34,7 @@ class Mage_AmazonPayments_Model_Payment_Asp extends Mage_Payment_Model_Method_Abstract { /** - * rewrited for Mage_Payment_Model_Method_Abstract + * rewrited for Mage_Payment_Model_Method_Abstract */ protected $_isGateway = false; protected $_canAuthorize = false; @@ -51,12 +51,12 @@ class Mage_AmazonPayments_Model_Payment_Asp extends Mage_Payment_Model_Method_Ab * rewrited for Mage_Payment_Model_Method_Abstract */ protected $_formBlockType = 'amazonpayments/asp_form'; - + /** * rewrited for Mage_Payment_Model_Method_Abstract */ protected $_code = 'amazonpayments_asp'; - + /** * current order */ @@ -68,19 +68,12 @@ class Mage_AmazonPayments_Model_Payment_Asp extends Mage_Payment_Model_Method_Ab * @param string $path * @return string */ - public function getConfig($path) + public function getConfig($path) { return Mage::getStoreConfig('payment/' . $this->_code . '/' . $path); - } - - /** - * rewrited for Mage_Payment_Model_Method_Abstract - */ - public function isAvailable($quote=null) - { - return $this->getConfig('active'); } + /** * Get singleton with AmazonPayments ASP API Model * @@ -162,12 +155,12 @@ protected function _mail($template, array $variables = array()) $this->getConfig('report_email'), null, $variables - ); + ); return $this; } - + /** - * rewrited for Mage_Payment_Model_Method_Abstract + * rewrited for Mage_Payment_Model_Method_Abstract */ public function getOrderPlaceRedirectUrl() { @@ -183,9 +176,9 @@ public function getPayRedirectUrl() { return $this->getApi()->getPayUrl(); } - + /** - * Return choice method description + * Return choice method description * * @return string */ @@ -195,7 +188,7 @@ public function getChoiceMethodDescription() } /** - * Return redirect message + * Return redirect message * * @return string */ @@ -203,7 +196,7 @@ public function getRedirectMessage() { return $this->getConfig('redirect_message'); } - + /** * Return pay params for current order * @@ -213,14 +206,14 @@ public function getPayRedirectParams() { $orderId = $this->getOrder()->getRealOrderId(); $amount = Mage::app()->getStore()->roundPrice($this->getOrder()->getBaseGrandTotal()); - $currencyCode = $this->getOrder()->getBaseCurrency(); - + $currencyCode = $this->getOrder()->getBaseCurrencyCode(); + $urlModel = Mage::getModel('core/url') ->setUseSession(false); - + return $this->getApi()->getPayParams( - $orderId, - $amount, + $orderId, + $amount, $currencyCode, $urlModel->getUrl('amazonpayments/asp/returnCancel'), $urlModel->getUrl('amazonpayments/asp/returnSuccess'), @@ -229,8 +222,8 @@ public function getPayRedirectParams() } /** - * When a customer redirect to Amazon Simple Pay site - * + * When a customer redirect to Amazon Simple Pay site + * * @return Mage_AmazonPayments_Model_Payment_Asp */ public function processEventRedirect() @@ -243,9 +236,9 @@ public function processEventRedirect() } /** - * When a customer successfully returned from Amazon Simple Pay site + * When a customer successfully returned from Amazon Simple Pay site * - * @return Mage_AmazonPayments_Model_Payment_Asp + * @return Mage_AmazonPayments_Model_Payment_Asp */ public function processEventReturnSuccess() { @@ -258,8 +251,8 @@ public function processEventReturnSuccess() } /** - * Customer canceled payment and successfully returned from Amazon Simple Pay site - * + * Customer canceled payment and successfully returned from Amazon Simple Pay site + * * @return Mage_AmazonPayments_Model_Payment_Asp */ public function processEventReturnCancel() @@ -272,7 +265,7 @@ public function processEventReturnCancel() } /** - * rewrited for Mage_Payment_Model_Method_Abstract + * rewrited for Mage_Payment_Model_Method_Abstract */ public function initialize($paymentAction, $stateObject) { @@ -294,7 +287,7 @@ public function processNotification($requestParams) if ($this->getConfig('debug_log')) { $this->_log('DEBUG ASP notification: ' . print_r($requestParams, 1)); } - + try { $this->getNotification()->setPayment($this)->process($requestParams); } catch(Exception $e) { @@ -304,17 +297,17 @@ public function processNotification($requestParams) if ($this->getConfig('report_error_to_email')) { $variables = array(); - $variables['request'] = print_r($requestParams, 1); - $variables['error'] = $e->getMessage(); + $variables['request'] = print_r($requestParams, 1); + $variables['error'] = $e->getMessage(); $this->_mail('email_template_notofication_error', $variables); } } - + return $this; } /** - * rewrited for Mage_Payment_Model_Method_Abstract + * rewrited for Mage_Payment_Model_Method_Abstract */ public function capture(Varien_Object $payment, $amount) { @@ -327,7 +320,7 @@ public function capture(Varien_Object $payment, $amount) } /** - * rewrited for Mage_Payment_Model_Method_Abstract + * rewrited for Mage_Payment_Model_Method_Abstract */ public function processInvoice($invoice, $payment) { @@ -336,7 +329,7 @@ public function processInvoice($invoice, $payment) is_null($invoice->getTransactionId())) { $amount = Mage::app()->getStore()->roundPrice($invoice->getBaseGrandTotal()); - $currencyCode = $payment->getOrder()->getBaseCurrency(); + $currencyCode = $payment->getOrder()->getBaseCurrencyCode(); $transactionId = $payment->getCcTransId(); $response = $this->getApi() ->setStoreId($payment->getOrder()->getStoreId()) @@ -368,7 +361,7 @@ public function processInvoice($invoice, $payment) } /** - * rewrited for Mage_Payment_Model_Method_Abstract + * rewrited for Mage_Payment_Model_Method_Abstract */ public function processCreditmemo($creditmemo, $payment) { @@ -379,7 +372,7 @@ public function processCreditmemo($creditmemo, $payment) is_null($creditmemo->getTransactionId())) { $amount = Mage::app()->getStore()->roundPrice($creditmemo->getBaseGrandTotal()); - $currencyCode = $payment->getOrder()->getBaseCurrency(); + $currencyCode = $payment->getOrder()->getBaseCurrencyCode(); $referenseID = $creditmemo->getInvoice()->getIncrementId(); $response = $this->getApi() ->setStoreId($payment->getOrder()->getStoreId()) @@ -408,7 +401,7 @@ public function processCreditmemo($creditmemo, $payment) } /** - * rewrited for Mage_Payment_Model_Method_Abstract + * rewrited for Mage_Payment_Model_Method_Abstract */ public function cancel(Varien_Object $payment) { diff --git a/app/code/core/Mage/AmazonPayments/Model/Payment/Asp/Notification.php b/app/code/core/Mage/AmazonPayments/Model/Payment/Asp/Notification.php index 3a6e42ecba..2956e793b7 100644 --- a/app/code/core/Mage/AmazonPayments/Model/Payment/Asp/Notification.php +++ b/app/code/core/Mage/AmazonPayments/Model/Payment/Asp/Notification.php @@ -360,13 +360,10 @@ protected function _processRefundSuccessful($request, $order) */ protected function _processRefundFailed($request, $order) { - $order->setState( - $order->getState(), - true, - Mage::helper('amazonpayments')->__('Amazon Simple Pay service payment confirmation failed'), - $notified = false + $order->addStatusToHistory( + $order->getStatus(), + Mage::helper('amazonpayments')->__('Amazon Simple Pay service payment confirmation failed') ); - return true; } diff --git a/app/code/core/Mage/AmazonPayments/Model/Payment/Cba.php b/app/code/core/Mage/AmazonPayments/Model/Payment/Cba.php index c8ec4a4b61..0ec1ab6da9 100644 --- a/app/code/core/Mage/AmazonPayments/Model/Payment/Cba.php +++ b/app/code/core/Mage/AmazonPayments/Model/Payment/Cba.php @@ -50,16 +50,6 @@ class Mage_AmazonPayments_Model_Payment_Cba extends Mage_Payment_Model_Method_Ab protected $_skipProccessDocument = false; - /** - * Return true if the method can be used at this time - * - * @return bool - */ - public function isAvailable($quote=null) - { - return Mage::getStoreConfig('payment/amazonpayments_cba/active'); - } - /** * Get checkout session namespace * @@ -447,7 +437,7 @@ protected function _createNewOrder(array $newOrderDetails) } $order->save(); - + $quote->setIsActive(false); $quote->save(); diff --git a/app/code/core/Mage/AmazonPayments/etc/config.xml b/app/code/core/Mage/AmazonPayments/etc/config.xml index a92a921461..72dda720dc 100644 --- a/app/code/core/Mage/AmazonPayments/etc/config.xml +++ b/app/code/core/Mage/AmazonPayments/etc/config.xml @@ -55,20 +55,7 @@ Mage_AmazonPayments Mage_AmazonPayments_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - Mage_AmazonPayments_Block diff --git a/app/code/core/Mage/AmazonPayments/etc/system.xml b/app/code/core/Mage/AmazonPayments/etc/system.xml index bd91ba946b..1155b35a54 100644 --- a/app/code/core/Mage/AmazonPayments/etc/system.xml +++ b/app/code/core/Mage/AmazonPayments/etc/system.xml @@ -36,12 +36,12 @@ Signing up with Checkout by Amazon To configure Checkout by Amazon™ you will need to enter your Checkout by Amazon™ Merchant ID, Access Key ID, and Secret Access Key. -If you do not already have a Checkout by Amazon™ account, click here to create one now. Sign-Up +If you do not already have a Checkout by Amazon™ account, click here to create one now. Sign-Up To locate your Merchant ID, sign in to your Seller Central Checkout by Amazon™ account and click Settings > Checkout Pipeline Settings. To locate your Access Key ID and Secret Access Key, sign in to your Seller Central Checkout by Amazon™ account and click Integration > AWS Key. Click the link to read the Amazon Web Services Customer Agreement, and then click the check box, if you are setting up a new Access Key ID. To enable XML Order Reports click Settings > Checkout Pipeline Settings, and then clicking Edit under the Order Report Settings section. Select Order Report Type as XML to get XML Order Reports using SOAP APIs. Configure your downloads for hourly. For additional information on setting up your Checkout by Amazon account, Click Here - FAQ. -Signup for Checkout by Amazon +Signup for Checkout by Amazon ]]> diff --git a/app/code/core/Mage/Api/etc/adminhtml.xml b/app/code/core/Mage/Api/etc/adminhtml.xml new file mode 100644 index 0000000000..3a3e383f48 --- /dev/null +++ b/app/code/core/Mage/Api/etc/adminhtml.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + Web Services + 0 + + + Users + 10 + + + Roles + 20 + + + + + + + Magento Core Api Section + + + + + + + + + + + + + + Web Services + 25 + + + Users + adminhtml/api_user + + + Roles + adminhtml/api_role + + + + + + + diff --git a/app/code/core/Mage/Api/etc/config.xml b/app/code/core/Mage/Api/etc/config.xml index 10bc616533..cae2702d7e 100644 --- a/app/code/core/Mage/Api/etc/config.xml +++ b/app/code/core/Mage/Api/etc/config.xml @@ -75,20 +75,7 @@ Mage_Api - - core_setup - - - - core_write - - - - - core_read - - @@ -117,39 +104,6 @@ - - - - - - - - Web Services - 0 - - - Users - 10 - - - Roles - 20 - - - - - - - Magento Core Api Section - - - - - - - - - @@ -159,26 +113,6 @@ - - - - - Web Services - 25 - - - Users - adminhtml/api_user - - - Roles - adminhtml/api_role - - - - - - diff --git a/app/code/core/Mage/Api/etc/wsdl2.xml b/app/code/core/Mage/Api/etc/wsdl2.xml index 9893b25bce..7aa17e6054 100644 --- a/app/code/core/Mage/Api/etc/wsdl2.xml +++ b/app/code/core/Mage/Api/etc/wsdl2.xml @@ -154,12 +154,12 @@ - List of resource faults + List of global faults - List of global faults + List of resource faults diff --git a/app/code/core/Mage/Backup/Model/Mysql4/Db.php b/app/code/core/Mage/Backup/Model/Mysql4/Db.php index 6883427017..6c76f15c98 100644 --- a/app/code/core/Mage/Backup/Model/Mysql4/Db.php +++ b/app/code/core/Mage/Backup/Model/Mysql4/Db.php @@ -324,10 +324,12 @@ 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: {$dbConfig['host']} Database: {$dbConfig['dbname']}\n" + . "-- Host: {$hostName} Database: {$dbConfig['dbname']}\n" . "-- ------------------------------------------------------\n" . "-- Server version: {$versionRow['Value']}\n\n" . "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n" diff --git a/app/code/core/Mage/Backup/etc/adminhtml.xml b/app/code/core/Mage/Backup/etc/adminhtml.xml new file mode 100644 index 0000000000..ff8474583a --- /dev/null +++ b/app/code/core/Mage/Backup/etc/adminhtml.xml @@ -0,0 +1,62 @@ + + + + + + + + + + Backups + adminhtml/system_backup + + + + + + + + + + + + + + + + Backups + + + + + + + + + + diff --git a/app/code/core/Mage/Backup/etc/config.xml b/app/code/core/Mage/Backup/etc/config.xml index 157f4777c1..78b4cdd369 100644 --- a/app/code/core/Mage/Backup/etc/config.xml +++ b/app/code/core/Mage/Backup/etc/config.xml @@ -46,37 +46,10 @@ Mage_Backup - - core_setup - - - - core_write - - - - - core_read - - - - - - - - - Backups - adminhtml/system_backup - - - - - - @@ -86,24 +59,5 @@ - - - - - - - - - - Backups - - - - - - - - - \ No newline at end of file diff --git a/app/code/core/Mage/Bundle/Model/Mysql4/Bundle.php b/app/code/core/Mage/Bundle/Model/Mysql4/Bundle.php index 0cf5cf0021..4ceef686ef 100644 --- a/app/code/core/Mage/Bundle/Model/Mysql4/Bundle.php +++ b/app/code/core/Mage/Bundle/Model/Mysql4/Bundle.php @@ -98,4 +98,19 @@ public function dropAllUnneededSelections($productId, $ids) ->query("DELETE FROM ".$this->getTable('bundle/selection')." WHERE `parent_product_id` = ". $productId . ( count($ids) > 0 ? " and selection_id not in (" . implode(',', $ids) . ")": '')); } + + /** + * Save product relations + * + * @param int $parentId + * @param array $childIds + * @return Mage_Bundle_Model_Mysql4_Bundle + */ + public function saveProductRelations($parentId, $childIds) + { + Mage::getResourceSingleton('catalog/product_relation') + ->processRelations($parentId, $childIds); + + return $this; + } } diff --git a/app/code/core/Mage/Bundle/Model/Mysql4/Indexer/Price.php b/app/code/core/Mage/Bundle/Model/Mysql4/Indexer/Price.php new file mode 100644 index 0000000000..75421760e0 --- /dev/null +++ b/app/code/core/Mage/Bundle/Model/Mysql4/Indexer/Price.php @@ -0,0 +1,438 @@ + + */ +class Mage_Bundle_Model_Mysql4_Indexer_Price + extends Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default +{ + /** + * Reindex temporary (price result data) for all products + * + * @return Mage_Bundle_Model_Mysql4_Indexer_Price + */ + public function reindexAll() + { + $this->_prepareBundlePrice(); + + return $this; + } + + /** + * Reindex temporary (price result data) for defined product(s) + * + * @param int|array $entityIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + public function reindexEntity($entityIds) + { + $this->_prepareBundlePrice($entityIds); + + return $this; + } + + /** + * Retrieve temporary price index table name for fixed bundle products + * + * @return string + */ + protected function _getBundlePriceTable() + { + return $this->getMainTable() . '_bundle'; + } + + /** + * Prepare temporary price index table for fixed bundle products + * + * @return Mage_Bundle_Model_Mysql4_Indexer_Price + */ + protected function _prepareBundlePriceTable() + { + $write = $this->_getWriteAdapter(); + $table = $this->_getBundlePriceTable(); + + $query = sprintf('DROP TABLE IF EXISTS %s', $write->quoteIdentifier($table)); + $write->query($query); + + $query = sprintf('CREATE TABLE %s (' + . ' `entity_id` INT(10) UNSIGNED NOT NULL,' + . ' `customer_group_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `website_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `tax_class_id` SMALLINT(5) UNSIGNED DEFAULT \'0\',' + . ' `price_type` TINYINT(1) UNSIGNED NOT NULL,' + . ' `special_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `tier_percent` DECIMAL(12,4) DEFAULT NULL,' + . ' `orig_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `price` DECIMAL(12,4) DEFAULT NULL,' + . ' `min_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `max_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `tier_price` DECIMAL(12,4) DEFAULT NULL,' + . ' PRIMARY KEY (`entity_id`,`customer_group_id`,`website_id`)' + . ') ENGINE=MYISAM DEFAULT CHARSET=utf8', + $write->quoteIdentifier($table)); + $write->query($query); + + return $this; + } + + /** + * Retrieve table name for temporary bundle selection prices index + * + * @return string + */ + protected function _getBundleSelectionTable() + { + return $this->getMainTable() . '_bundle_selection'; + } + + /** + * Retrieve table name for temporary bundle option prices index + * + * @return string + */ + protected function _getBundleOptionTable() + { + return $this->getMainTable() . '_bundle_option'; + } + + /** + * Prepare table structure for temporary bundle selection prices index + * + * @return Mage_Bundle_Model_Mysql4_Indexer_Price + */ + protected function _prepareBundleSelectionTable() + { + $write = $this->_getWriteAdapter(); + $table = $this->_getBundleSelectionTable(); + + $query = sprintf('DROP TABLE IF EXISTS %s', $write->quoteIdentifier($table)); + $write->query($query); + + $query = sprintf('CREATE TABLE %s (' + . ' `entity_id` INT(10) UNSIGNED NOT NULL,' + . ' `customer_group_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `website_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `option_id` INT(10) UNSIGNED DEFAULT \'0\',' + . ' `selection_id` INT(10) UNSIGNED DEFAULT \'0\',' + . ' `group_type` TINYINT(1) UNSIGNED DEFAULT \'0\',' + . ' `is_required` TINYINT(1) UNSIGNED DEFAULT \'0\',' + . ' `price` DECIMAL(12,4) DEFAULT NULL,' + . ' `tier_price` DECIMAL(12,4) DEFAULT NULL,' + . ' PRIMARY KEY (`entity_id`,`customer_group_id`,`website_id`, `option_id`, `selection_id`)' + . ') ENGINE=MYISAM DEFAULT CHARSET=utf8', + $write->quoteIdentifier($table)); + $write->query($query); + + return $this; + } + + /** + * Prepare table structure for temporary bundle option prices index + * + * @return Mage_Bundle_Model_Mysql4_Indexer_Price + */ + protected function _prepareBundleOptionTable() + { + $write = $this->_getWriteAdapter(); + $table = $this->_getBundleOptionTable(); + + $query = sprintf('DROP TABLE IF EXISTS %s', $write->quoteIdentifier($table)); + $write->query($query); + + $query = sprintf('CREATE TABLE %s (' + . ' `entity_id` INT(10) UNSIGNED NOT NULL,' + . ' `customer_group_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `website_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `option_id` INT(10) UNSIGNED DEFAULT \'0\',' + . ' `min_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `alt_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `max_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `tier_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `alt_tier_price` DECIMAL(12,4) DEFAULT NULL,' + . ' PRIMARY KEY (`entity_id`,`customer_group_id`,`website_id`, `option_id`)' + . ') ENGINE=MYISAM DEFAULT CHARSET=utf8', + $write->quoteIdentifier($table)); + $write->query($query); + + return $this; + } + + /** + * Prepare temporary price index data for bundle products by price type + * + * @param int $priceType + * @param int|array $entityIds the entity ids limitatation + * @return Mage_Bundle_Model_Mysql4_Indexer_Price + */ + protected function _prepareBundlePriceByType($priceType, $entityIds = null) + { + $write = $this->_getWriteAdapter(); + $table = $this->_getBundlePriceTable(); + + $select = $write->select() + ->from(array('e' => $this->getTable('catalog/product')), array('entity_id')) + ->join( + array('cg' => $this->getTable('customer/customer_group')), + '', + array('customer_group_id')); + $this->_addWebsiteJoinToSelect($select, true); + $this->_addProductWebsiteJoinToSelect($select, 'cw.website_id', 'e.entity_id'); + $select->columns('website_id', 'cw') + ->join( + array('cwd' => $this->_getWebsiteDateTable()), + 'cw.website_id = cwd.website_id', + array()) + ->joinLeft( + array('tp' => $this->_getTierPriceIndexTable()), + 'tp.entity_id = e.entity_id AND tp.website_id = cw.website_id' + . ' AND tp.customer_group_id = cg.customer_group_id', + array()) + ->where('e.type_id=?', $this->getTypeId()); + + // add enable products limitation + $statusCond = $write->quoteInto('=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); + $this->_addAttributeToSelect($select, 'status', 'e.entity_id', 'cs.store_id', $statusCond, true); + + $taxClassId = $this->_addAttributeToSelect($select, 'tax_class_id', 'e.entity_id', 'cs.store_id'); + $select->columns(array('tax_class_id' => new Zend_Db_Expr("IF($taxClassId IS NOT NULL, $taxClassId, 0)"))); + + $priceTypeCond = $write->quoteInto('=?', $priceType); + $this->_addAttributeToSelect($select, 'price_type', 'e.entity_id', 'cs.store_id', $priceTypeCond); + + $price = $this->_addAttributeToSelect($select, 'price', 'e.entity_id', 'cs.store_id'); + $specialPrice = $this->_addAttributeToSelect($select, 'special_price', 'e.entity_id', 'cs.store_id'); + $specialFrom = $this->_addAttributeToSelect($select, 'special_from_date', 'e.entity_id', 'cs.store_id'); + $specialTo = $this->_addAttributeToSelect($select, 'special_to_date', 'e.entity_id', 'cs.store_id'); + $curentDate = new Zend_Db_Expr('cwd.date'); + + $specialExpr = new Zend_Db_Expr("IF(IF({$specialFrom} IS NULL, 1, " + . "IF({$specialFrom} <= {$curentDate}, 1, 0)) > 0 AND IF({$specialTo} IS NULL, 1, " + . "IF({$specialTo} >= {$curentDate}, 1, 0)) > 0 AND {$specialPrice} > 0, $specialPrice, 0)"); + $tierExpr = new Zend_Db_Expr("tp.min_price"); + + if ($priceType == Mage_Bundle_Model_Product_Price::PRICE_TYPE_FIXED) { + $finalPrice = new Zend_Db_Expr("IF({$specialExpr} > 0," + . " ROUND($price * ({$specialExpr} / 100), 4), {$price})"); + $tierPrice = new Zend_Db_Expr("IF({$tierExpr} IS NOT NULL," + . " ROUND({$price} * ({$tierExpr} / 100), 4), NULL)"); + } else { + $finalPrice = new Zend_Db_Expr("0"); + $tierPrice = new Zend_Db_Expr("IF({$tierExpr} IS NOT NULL, 0, NULL)"); + } + + $select->columns(array( + 'price_type' => new Zend_Db_Expr($priceType), + 'special_price' => $specialExpr, + 'tier_percent' => $tierExpr, + 'price' => $finalPrice, + 'orig_price' => new Zend_Db_Expr("IF({$price} IS NULL, 0, {$price})"), + 'min_price' => $finalPrice, + 'max_price' => $finalPrice, + 'tier_price' => $tierPrice + )); + + if (!is_null($entityIds)) { + $select->where('e.entity_id IN(?)', $entityIds); + } + + /** + * Add additional external limitation + */ + Mage::dispatchEvent('prepare_catalog_product_index_select', array( + 'select' => $select, + 'entity_field' => new Zend_Db_Expr('e.entity_id'), + 'website_field' => new Zend_Db_Expr('cw.website_id'), + 'store_field' => new Zend_Db_Expr('cs.store_id') + )); + + $query = $select->insertFromSelect($table); + $write->query($query); + + return $this; + } + + /** + * Calculate fixed bundle product selections price + * + * @return Mage_Bundle_Model_Mysql4_Indexer_Price + */ + protected function _calculateBundleOptionPrice() + { + $write = $this->_getWriteAdapter(); + + $this->_prepareBundleSelectionTable(); + $this->_calculateBundleSelectionPrice(Mage_Bundle_Model_Product_Price::PRICE_TYPE_FIXED); + $this->_calculateBundleSelectionPrice(Mage_Bundle_Model_Product_Price::PRICE_TYPE_DYNAMIC); + + $this->_prepareBundleOptionTable(); + + $select = $write->select() + ->from( + array('i' => $this->_getBundleSelectionTable()), + array('entity_id', 'customer_group_id', 'website_id', 'option_id')) + ->group(array('entity_id', 'customer_group_id', 'website_id', 'option_id')) + ->columns(array( + 'min_price' => new Zend_Db_Expr("IF(i.is_required = 1, MIN(i.price), 0)"), + 'alt_price' => new Zend_Db_Expr("IF(i.is_required = 0, MIN(i.price), 0)"), + 'max_price' => new Zend_Db_Expr("IF(i.group_type = 1, SUM(i.price), MAX(i.price))"), + 'tier_price' => new Zend_Db_Expr("IF(i.is_required = 1, MIN(i.tier_price), 0)"), + 'alt_tier_price' => new Zend_Db_Expr("IF(i.is_required = 0, MIN(i.tier_price), 0)"), + )); + + $query = $select->insertFromSelect($this->_getBundleOptionTable()); + $write->query($query); + + $this->_prepareDefaultFinalPriceTable(); + + $minPrice = new Zend_Db_Expr("IF(SUM(io.min_price) = 0, SUM(io.alt_price), SUM(io.min_price)) + i.price"); + $maxPrice = new Zend_Db_Expr("SUM(io.max_price) + i.price"); + $tierPrice = new Zend_Db_Expr("IF(i.tier_percent IS NOT NULL, IF(SUM(io.tier_price) = 0, " + . "SUM(io.alt_tier_price), SUM(io.tier_price)) + i.tier_price, NULL)"); + + $select = $write->select() + ->from( + array('io' => $this->_getBundleOptionTable()), + array('entity_id', 'customer_group_id', 'website_id')) + ->join( + array('i' => $this->_getBundlePriceTable()), + 'i.entity_id = io.entity_id AND i.customer_group_id = io.customer_group_id' + . ' AND i.website_id = io.website_id', + array()) + ->group(array('io.entity_id', 'io.customer_group_id', 'io.website_id')) + ->columns(array('i.tax_class_id', + 'orig_price' => 'i.orig_price', + 'price' => 'i.price', + 'min_price' => $minPrice, + 'max_price' => $maxPrice, + 'tier_price' => $tierPrice + )); + + $query = $select->insertFromSelect($this->_getDefaultFinalPriceTable()); + $write->query($query); + + return $this; + } + + /** + * Calculate bundle product selections price by product type + * + * @param int $priceType + * @return Mage_Bundle_Model_Mysql4_Indexer_Price + */ + protected function _calculateBundleSelectionPrice($priceType) + { + $write = $this->_getWriteAdapter(); + + if ($priceType == Mage_Bundle_Model_Product_Price::PRICE_TYPE_FIXED) { + $priceExpr = new Zend_Db_Expr("IF(bs.selection_price_type = 1, " + . "ROUND(i.price * (bs.selection_price_value / 100), 4), IF(i.special_price > 0, " + . "ROUND(bs.selection_price_value * (i.special_price / 100), 4), bs.selection_price_value)) " + . "* bs.selection_qty"); + $tierExpr = new Zend_Db_Expr("IF(i.tier_price IS NOT NULL, IF(bs.selection_price_type = 1, " + . "ROUND(i.tier_price * (bs.selection_price_value / 100), 4), IF(i.tier_percent > 0, " + . "ROUND(bs.selection_price_value * (i.tier_percent / 100), 4), bs.selection_price_value)) " + . "* bs.selection_qty, NULL)"); + } else { + $priceExpr = new Zend_Db_Expr("IF(i.special_price > 0, ROUND(idx.min_price * (i.special_price / 100), 4), " + . "idx.min_price) * bs.selection_qty"); + $tierExpr = new Zend_Db_Expr("IF(i.tier_price IS NOT NULL, ROUND(idx.min_price * (i.tier_price / 100), 4) " + . "* bs.selection_qty, NULL)"); + } + + $select = $write->select() + ->from( + array('i' => $this->_getBundlePriceTable()), + array('entity_id', 'customer_group_id', 'website_id')) + ->join( + array('bo' => $this->getTable('bundle/option')), + 'bo.parent_id = i.entity_id', + array('option_id')) + ->join( + array('bs' => $this->getTable('bundle/selection')), + 'bs.option_id = bo.option_id', + array('selection_id')) + ->join( + array('idx' => $this->getIdxTable()), + 'bs.product_id = idx.entity_id AND i.customer_group_id = idx.customer_group_id' + . ' AND i.website_id = idx.website_id', + array()) + ->where('i.price_type=?', $priceType) + ->columns(array( + 'group_type' => new Zend_Db_Expr("IF(bo.type = 'select' OR bo.type = 'radio', 0, 1)"), + 'is_required' => 'bo.required', + 'price' => $priceExpr, + 'tier_price' => $tierExpr, + )); + + $query = $select->insertFromSelect($this->_getBundleSelectionTable()); + $write->query($query); + + return $this; + } + + /** + * Prepare temporary index price for bundle products + * + * @param int|array $entityIds the entity ids limitation + * @return Mage_Bundle_Model_Mysql4_Indexer_Price + */ + protected function _prepareBundlePrice($entityIds = null) + { + $this->_prepareWebsiteDateTable(); + $this->_prepareBundlePriceTable(); + $this->_prepareBundlePriceByType(Mage_Bundle_Model_Product_Price::PRICE_TYPE_FIXED, $entityIds); + $this->_prepareBundlePriceByType(Mage_Bundle_Model_Product_Price::PRICE_TYPE_DYNAMIC, $entityIds); + + /** + * Add possibility modify prices from external events + */ + $select = $this->_getWriteAdapter()->select() + ->join(array('wd' => $this->_getWebsiteDateTable()), + 'i.website_id = wd.website_id', + array()); + Mage::dispatchEvent('prepare_catalog_product_price_index_table', array( + 'index_table' => array('i' => $this->_getDefaultFinalPriceTable()), + 'select' => $select, + 'entity_id' => 'i.entity_id', + 'customer_group_id' => 'i.customer_group_id', + 'website_id' => 'i.website_id', + 'website_date' => 'wd.date', + 'update_fields' => array('price', 'min_price', 'max_price') + )); + + $this->_calculateBundleOptionPrice(); + $this->_applyCustomOption(); + $this->_movePriceDataToIndexTable(); + + return $this; + } +} diff --git a/app/code/core/Mage/Bundle/Model/Mysql4/Indexer/Stock.php b/app/code/core/Mage/Bundle/Model/Mysql4/Indexer/Stock.php new file mode 100644 index 0000000000..b9e9d43b96 --- /dev/null +++ b/app/code/core/Mage/Bundle/Model/Mysql4/Indexer/Stock.php @@ -0,0 +1,199 @@ + + */ +class Mage_Bundle_Model_Mysql4_Indexer_Stock extends Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Default +{ +/** + * Reindex temporary (price result data) for all products + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + public function reindexAll() + { + $this->_prepareIndexTable(); + return $this; + } + + /** + * Reindex temporary (price result data) for defined product(s) + * + * @param int|array $entityIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + public function reindexEntity($entityIds) + { + $this->_prepareIndexTable($entityIds); + + return $this; + } + + /** + * Retrieve table name for temporary bundle option stock index + * + * @return string + */ + protected function _getBundleOptionTable() + { + return $this->getMainTable() . '_bundle_option'; + } + + /** + * Prepare table structure for temporary bundle option stock index + * + * @return Mage_Bundle_Model_Mysql4_Indexer_Price + */ + protected function _prepareBundleOptionTable() + { + $write = $this->_getWriteAdapter(); + $table = $this->_getBundleOptionTable(); + + $query = sprintf('DROP TABLE IF EXISTS %s', $write->quoteIdentifier($table)); + $write->query($query); + + $query = sprintf('CREATE TABLE %s (' + . ' `entity_id` INT(10) UNSIGNED NOT NULL,' + . ' `website_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `stock_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `option_id` INT(10) UNSIGNED DEFAULT \'0\',' + . ' `stock_status` TINYINT(1) DEFAULT 0,' + . ' PRIMARY KEY (`entity_id`,`stock_id`,`website_id`, `option_id`)' + . ') ENGINE=MYISAM DEFAULT CHARSET=utf8', + $write->quoteIdentifier($table)); + $write->query($query); + + return $this; + } + + /** + * Prepare stock status per Bundle options, website and stock + * + * @param int|array $entityIds + * @return Mage_Bundle_Model_Mysql4_Indexer_Stock + */ + protected function _prepareBundleOptionStockData($entityIds = null) + { + $write = $this->_getWriteAdapter(); + $this->_prepareBundleOptionTable(); + + $select = $write->select() + ->from(array('bo' => $this->getTable('bundle/option')), array('parent_id')); + $this->_addWebsiteJoinToSelect($select, false); + $select->columns('website_id', 'cw') + ->join( + array('cis' => $this->getTable('cataloginventory/stock')), + '', + array('stock_id')) + ->join( + array('bs' => $this->getTable('bundle/selection')), + 'bs.option_id = bo.option_id', + array()) + ->joinLeft( + array('i' => $this->getIdxTable()), + 'i.product_id = bs.product_id AND i.website_id = cw.website_id AND i.stock_id = cis.stock_id', + array()) + ->where('cw.website_id != 0') + ->where('bo.required = ?', 1) + ->group(array('bo.parent_id', 'cw.website_id', 'cis.stock_id', 'bo.option_id')) + ->columns(array( + 'option_id' => 'bo.option_id', + 'status' => new Zend_Db_Expr("MAX(i.stock_status)") + )); + + if (!is_null($entityIds)) { + $select->where('bo.parent_id IN(?)', $entityIds); + } + + $query = $select->insertFromSelect($this->_getBundleOptionTable()); + $write->query($query); + + return $this; + } + + /** + * Prepare stock status data in temporary index table + * + * @param int|array $entityIds the product limitation + * @return Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Configurable + */ + protected function _prepareIndexTable($entityIds = null) + { + $this->_prepareBundleOptionStockData($entityIds); + $write = $this->_getWriteAdapter(); + + $select = $write->select() + ->from(array('e' => $this->getTable('catalog/product')), array('entity_id')); + $this->_addWebsiteJoinToSelect($select, true); + $this->_addProductWebsiteJoinToSelect($select, 'cw.website_id', 'e.entity_id'); + $select->columns('cw.website_id') + ->join( + array('cis' => $this->getTable('cataloginventory/stock')), + '', + array('stock_id')) + ->joinLeft( + array('cisi' => $this->getTable('cataloginventory/stock_item')), + 'cisi.stock_id = cis.stock_id AND cisi.product_id = e.entity_id', + array()) + ->joinLeft( + array('o' => $this->_getBundleOptionTable()), + 'o.entity_id = e.entity_id AND o.website_id = cw.website_id AND o.stock_id = cis.stock_id', + array()) + ->columns(array('qty' => new Zend_Db_Expr('0'))) + ->where('cw.website_id != 0') + ->where('e.type_id = ?', $this->getTypeId()) + ->group(array('e.entity_id', 'cw.website_id', 'cis.stock_id')); + + // add limitation of status + $condition = $write->quoteInto('=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); + $this->_addAttributeToSelect($select, 'status', 'e.entity_id', 'cs.store_id', $condition); + + if ($this->_isManageStock()) { + $statusExpr = new Zend_Db_Expr('IF(cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0,' + . ' 1, cisi.is_in_stock)'); + } else { + $statusExpr = new Zend_Db_Expr('IF(cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 1,' + . 'cisi.is_in_stock, 1)'); + } + + $select->columns(array('status' => new Zend_Db_Expr("LEAST(MAX(o.stock_status), {$statusExpr})"))); + + if (!is_null($entityIds)) { + $select->where('e.entity_id IN(?)', $entityIds); + } + + $query = $select->insertFromSelect($this->getIdxTable()); + $write->query($query); + + return $this; + } +} diff --git a/app/code/core/Mage/Bundle/Model/Mysql4/Price/Index.php b/app/code/core/Mage/Bundle/Model/Mysql4/Price/Index.php index 2459b64464..06b22da5b9 100644 --- a/app/code/core/Mage/Bundle/Model/Mysql4/Price/Index.php +++ b/app/code/core/Mage/Bundle/Model/Mysql4/Price/Index.php @@ -343,7 +343,7 @@ public function getProductsSalableStatus($products, Mage_Core_Model_Website $web 'e.entity_id=t1_status.entity_id' . ' AND t1_status.attribute_id=' . $status->getAttributeId() . ' AND t1_status.store_id=0', - array('status' => 'IFNULL(t2_status.value, t1_status.value)')) + array('status' => 'IF(t2_status.value_id > 0, t2_status.value, t1_status.value)')) ->joinLeft( array('t2_status' => $statusTable), 't1_status.entity_id = t2_status.entity_id' @@ -461,7 +461,7 @@ protected function _addAttributeDataToSelect(Varien_Db_Select $select, $attribut 'e.entity_id='.$tableGlobal.'.entity_id' . ' AND '.$tableGlobal.'.attribute_id=' . $attribute->getAttributeId() . ' AND '.$tableGlobal.'.store_id=0', - array($attribute->getAttributeCode() => 'IFNULL('.$tableStore.'.value, '.$tableGlobal.'.value)')) + array($attribute->getAttributeCode() => 'IF('.$tableStore.'.value_id > 0, '.$tableStore.'.value, '.$tableGlobal.'.value)')) ->joinLeft( array($tableStore => $tableName), $tableGlobal.'.entity_id = '.$tableStore.'.entity_id' diff --git a/app/code/core/Mage/Bundle/Model/Product/Price.php b/app/code/core/Mage/Bundle/Model/Product/Price.php index b05d0d3ae6..af393313e2 100644 --- a/app/code/core/Mage/Bundle/Model/Product/Price.php +++ b/app/code/core/Mage/Bundle/Model/Product/Price.php @@ -183,7 +183,6 @@ public function getPrices($product, $which = null) $prices[] = $value->getPrice(); } if (count($prices)) { - var_dump($prices); if ($customOption->getIsRequire()) { $minimalPrice += min($prices); } diff --git a/app/code/core/Mage/Bundle/Model/Product/Type.php b/app/code/core/Mage/Bundle/Model/Product/Type.php index eac0c050db..51a72b4dab 100644 --- a/app/code/core/Mage/Bundle/Model/Product/Type.php +++ b/app/code/core/Mage/Bundle/Model/Product/Type.php @@ -242,8 +242,12 @@ public function beforeSave($product = null) public function save($product = null) { parent::save($product); + /* @var $resource Mage_Bundle_Model_Mysql4_Bundle */ + $resource = Mage::getResourceModel('bundle/bundle'); - if ($options = $this->getProduct($product)->getBundleOptionsData()) { + $options = $this->getProduct($product)->getBundleOptionsData(); + if ($options) { + $this->getProduct($product)->setIsRelationsChanged(true); foreach ($options as $key => $option) { if (isset($option['option_id']) && $option['option_id'] == '') { @@ -261,9 +265,11 @@ public function save($product = null) $options[$key]['option_id'] = $optionModel->getOptionId(); } + $usedProductIds = array(); $excludeSelectionIds = array(); - if ($selections = $this->getProduct($product)->getBundleSelectionsData()) { + $selections = $this->getProduct($product)->getBundleSelectionsData(); + if ($selections) { foreach ($selections as $index => $group) { foreach ($group as $key => $selection) { if (isset($selection['selection_id']) && $selection['selection_id'] == '') { @@ -286,14 +292,17 @@ public function save($product = null) if ($selectionModel->getSelectionId()) { $excludeSelectionIds[] = $selectionModel->getSelectionId(); + $usedProductIds[] = $selectionModel->getProductId(); } } } - Mage::getResourceModel('bundle/bundle')->dropAllUnneededSelections($this->getProduct($product)->getId(), $excludeSelectionIds); + + $resource->dropAllUnneededSelections($this->getProduct($product)->getId(), $excludeSelectionIds); + $resource->saveProductRelations($this->getProduct($product)->getId(), array_unique($usedProductIds)); } if ($this->getProduct($product)->getData('price_type') != $this->getProduct($product)->getOrigData('price_type')) { - Mage::getResourceModel('bundle/bundle')->dropAllQuoteChildItems($this->getProduct($product)->getId()); + $resource->dropAllQuoteChildItems($this->getProduct($product)->getId()); } } @@ -353,10 +362,12 @@ public function getSelectionsCollection($optionIds, $product = null) $selectionsCollection = Mage::getResourceModel('bundle/selection_collection') ->addAttributeToSelect(Mage::getSingleton('catalog/config')->getProductAttributes()) ->setFlag('require_stock_items', true) + ->setFlag('product_children', true) ->setPositionOrder() ->addStoreFilter($this->getStoreFilter($product)) ->addFilterByRequiredOptions() ->setOptionIdsFilter($optionIds); + $this->getProduct($product)->setData($this->_keySelectionsCollection, $selectionsCollection); } return $this->getProduct($product)->getData($this->_keySelectionsCollection); @@ -788,10 +799,15 @@ public function shakeSelections($a, $b) */ public function hasOptions($product = null) { + $product = $this->getProduct($product); $this->setStoreFilter($product->getStoreId(), $product); - if (count($this->getSelectionsCollection($this->getOptionsCollection($product)->getAllIds(), $product)->getItems()) || $this->getProduct($product)->getOptions()) { + $optionIds = $this->getOptionsCollection($product)->getAllIds(); + $collection = $this->getSelectionsCollection($optionIds, $product); + + if (count($collection) > 0 || $product->getOptions()) { return true; } + return false; } diff --git a/app/code/core/Mage/Bundle/etc/config.xml b/app/code/core/Mage/Bundle/etc/config.xml index 61bbe2c904..91bc0279e3 100644 --- a/app/code/core/Mage/Bundle/etc/config.xml +++ b/app/code/core/Mage/Bundle/etc/config.xml @@ -28,7 +28,7 @@ - 0.1.8 + 0.1.10 @@ -61,20 +61,7 @@ Mage_Bundle Mage_Catalog_Model_Resource_Eav_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - Mage_Bundle_Block @@ -93,6 +80,8 @@ bundle/product_price bundle/catalogIndex_data_bundle 40 + bundle/indexer_price + bundle/indexer_stock @@ -142,7 +131,6 @@ - singleton bundle/observer catalogIndexPlainReindexAfter @@ -179,7 +167,6 @@ - singleton bundle/observer appendUpsellProducts @@ -188,7 +175,6 @@ - singleton bundle/observer appendBundleSelectionData @@ -197,7 +183,6 @@ - singleton bundle/observer loadProductOptions @@ -206,7 +191,6 @@ - singleton bundle/observer catalogProductLoadAfter @@ -260,7 +244,6 @@ - singleton bundle/observer prepareProductSave @@ -269,7 +252,6 @@ - singleton bundle/observer appendBundleSelectionData @@ -278,7 +260,6 @@ - singleton bundle/observer duplicateProduct @@ -287,7 +268,6 @@ - singleton bundle/observer setAttributeTabBlock @@ -296,7 +276,6 @@ - singleton bundle/observer setAttributeTabBlock diff --git a/app/code/core/Mage/Bundle/sql/bundle_setup/mysql4-upgrade-0.1.8-0.1.9.php b/app/code/core/Mage/Bundle/sql/bundle_setup/mysql4-upgrade-0.1.8-0.1.9.php new file mode 100644 index 0000000000..114492281f --- /dev/null +++ b/app/code/core/Mage/Bundle/sql/bundle_setup/mysql4-upgrade-0.1.8-0.1.9.php @@ -0,0 +1,50 @@ +startSetup(); + +$attributes = array( + $installer->getAttributeId('catalog_product', 'cost') +); + +$sql = $installer->getConnection()->quoteInto("SELECT * FROM `{$installer->getTable('catalog/eav_attribute')}` WHERE attribute_id IN (?)", $attributes); +$data = $installer->getConnection()->fetchAll($sql); + +foreach ($data as $row) { + $row['apply_to'] = array_flip(explode(',', $row['apply_to'])); + unset($row['apply_to']['bundle']); + $row['apply_to'] = implode(',', array_flip($row['apply_to'])); + + $installer->run("UPDATE `{$installer->getTable('catalog/eav_attribute')}` + SET `apply_to` = '{$row['apply_to']}' + WHERE `attribute_id` = {$row['attribute_id']}"); +} + +$installer->endSetup(); diff --git a/app/code/core/Mage/Bundle/sql/bundle_setup/mysql4-upgrade-0.1.9-0.1.10.php b/app/code/core/Mage/Bundle/sql/bundle_setup/mysql4-upgrade-0.1.9-0.1.10.php new file mode 100644 index 0000000000..d9309d80a7 --- /dev/null +++ b/app/code/core/Mage/Bundle/sql/bundle_setup/mysql4-upgrade-0.1.9-0.1.10.php @@ -0,0 +1,39 @@ +startSetup(); +$installer->run(" +INSERT IGNORE INTO `{$installer->getTable('catalog/product_relation')}` +SELECT + `parent_product_id`, + `product_id` +FROM `{$installer->getTable('bundle/selection')}`; +"); +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/Block/Category/Widget/Link.php b/app/code/core/Mage/Catalog/Block/Category/Widget/Link.php new file mode 100644 index 0000000000..5d1cb5920a --- /dev/null +++ b/app/code/core/Mage/Catalog/Block/Category/Widget/Link.php @@ -0,0 +1,46 @@ + + */ + +class Mage_Catalog_Block_Category_Widget_Link + extends Mage_Catalog_Block_Widget_Link +{ + /** + * Initialize entity model + */ + protected function _construct() + { + parent::_construct(); + $this->_entityResource = Mage::getResourceSingleton('catalog/category'); + } +} diff --git a/app/code/core/Mage/Catalog/Block/Navigation.php b/app/code/core/Mage/Catalog/Block/Navigation.php index 534feeb560..81a5a718b9 100644 --- a/app/code/core/Mage/Catalog/Block/Navigation.php +++ b/app/code/core/Mage/Catalog/Block/Navigation.php @@ -36,6 +36,13 @@ class Mage_Catalog_Block_Navigation extends Mage_Core_Block_Template { protected $_categoryInstance = null; + /** + * Array of level position counters + * + * @var array + */ + protected $_itemLevelPositions = array(); + protected function _construct() { $this->addData(array( @@ -136,6 +143,33 @@ public function getCategoryUrl($category) return $url; } + /** + * Return item position representation in menu tree + * + * @param int $level + * @return string + */ + protected function _getItemPosition($level) + { + if ($level == 0) { + $zeroLevelPosition = isset($this->_itemLevelPositions[$level]) ? $this->_itemLevelPositions[$level] + 1 : 1; + $this->_itemLevelPositions = array(); + $this->_itemLevelPositions[$level] = $zeroLevelPosition; + } elseif (isset($this->_itemLevelPositions[$level])) { + $this->_itemLevelPositions[$level]++; + } else { + $this->_itemLevelPositions[$level] = 1; + } + + $position = array(); + for($i = 0; $i <= $level; $i++) { + if (isset($this->_itemLevelPositions[$i])) { + $position[] = $this->_itemLevelPositions[$i]; + } + } + return implode('-', $position); + } + /** * Enter description here... * @@ -164,7 +198,8 @@ public function drawItem($category, $level=0, $last=false) } $html.= ' class="level'.$level; - $html.= ' nav-'.str_replace('/', '-', Mage::helper('catalog/category')->getCategoryUrlPath($category->getRequestPath())); + //$html.= ' nav-'.str_replace('/', '-', Mage::helper('catalog/category')->getCategoryUrlPath($category->getRequestPath())); + $html.= ' nav-' . $this->_getItemPosition($level); if ($this->isCategoryActive($category)) { $html.= ' active'; } diff --git a/app/code/core/Mage/Catalog/Block/Product/Abstract.php b/app/code/core/Mage/Catalog/Block/Product/Abstract.php index a5e6581d21..46f2ed9f38 100644 --- a/app/code/core/Mage/Catalog/Block/Product/Abstract.php +++ b/app/code/core/Mage/Catalog/Block/Product/Abstract.php @@ -90,7 +90,7 @@ public function getAddToCompareUrl($product) public function getMinimalQty($product) { if ($stockItem = $product->getStockItem()) { - return $stockItem->getMinSaleQty()>1 ? $stockItem->getMinSaleQty()*1 : null; + return $stockItem->getMinSaleQty()>0 ? $stockItem->getMinSaleQty()*1 : null; } return null; } diff --git a/app/code/core/Mage/Catalog/Block/Product/Compare/List.php b/app/code/core/Mage/Catalog/Block/Product/Compare/List.php index bf67135156..8cbe9b64b6 100644 --- a/app/code/core/Mage/Catalog/Block/Product/Compare/List.php +++ b/app/code/core/Mage/Catalog/Block/Product/Compare/List.php @@ -48,6 +48,24 @@ class Mage_Catalog_Block_Product_Compare_List extends Mage_Catalog_Block_Product */ protected $_attributes; + /** + * Retrieve url for adding product to wishlist with params + * + * @param Mage_Catalog_Model_Product $product + * @return string + */ + public function getAddToWishlistUrl($product) + { + $continueUrl = Mage::helper('core')->urlEncode($this->getUrl('customer/account')); + $urlParamName = Mage_Core_Controller_Front_Action::PARAM_NAME_URL_ENCODED; + + $params = array( + $urlParamName => $continueUrl + ); + + return $this->helper('wishlist')->getAddUrlWithParams($product, $params); + } + /** * Preparing layout * diff --git a/app/code/core/Mage/Catalog/Block/Product/New.php b/app/code/core/Mage/Catalog/Block/Product/New.php index 9e4a295215..fcd7231618 100644 --- a/app/code/core/Mage/Catalog/Block/Product/New.php +++ b/app/code/core/Mage/Catalog/Block/Product/New.php @@ -37,14 +37,44 @@ class Mage_Catalog_Block_Product_New extends Mage_Catalog_Block_Product_Abstract const DEFAULT_PRODUCTS_COUNT = 5; + /** + * Initialize block's cache + */ + protected function _construct() + { + parent::_construct(); + $this->addData(array( + 'cache_lifetime' => 86400, + 'cache_tags' => array(Mage_Catalog_Model_Product::CACHE_TAG), + )); + } + + /** + * Retrieve Key for caching block content + * + * @return string + */ + public function getCacheKey() + { + return 'CATALOG_PRODUCT_NEW_' . Mage::app()->getStore()->getId() + . '_' . Mage::getDesign()->getPackageName() + . '_' . Mage::getDesign()->getTheme('template') + . '_' . Mage::getSingleton('customer/session')->getCustomerGroupId() + . '_' . md5($this->getTemplate()); + } + + /** + * Prepare collection with new products and applied page limits. + * + * return Mage_Catalog_Block_Product_New + */ protected function _beforeToHtml() { $todayDate = Mage::app()->getLocale()->date()->toString(Varien_Date::DATETIME_INTERNAL_FORMAT); - + $collection = Mage::getResourceModel('catalog/product_collection'); - Mage::getSingleton('catalog/product_status')->addVisibleFilterToCollection($collection); - Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($collection); - + $collection->setVisibility(Mage::getSingleton('catalog/product_visibility')->getVisibleInCatalogIds()); + $collection = $this->_addProductAttributesAndPrices($collection) ->addStoreFilter() ->addAttributeToFilter('news_from_date', array('date' => true, 'to' => $todayDate)) @@ -56,17 +86,29 @@ protected function _beforeToHtml() ->setPageSize($this->getProductsCount()) ->setCurPage(1) ; + $this->setProductCollection($collection); return parent::_beforeToHtml(); } + /** + * Set how much product should be displayed at once. + * + * @param $count + * @return Mage_Catalog_Block_Product_New + */ public function setProductsCount($count) { $this->_productsCount = $count; return $this; } + /** + * Get how much products should be displayed at once. + * + * @return int + */ public function getProductsCount() { if (null === $this->_productsCount) { diff --git a/app/code/core/Mage/Catalog/Block/Product/View.php b/app/code/core/Mage/Catalog/Block/Product/View.php index b4e9b8da3f..049621f430 100644 --- a/app/code/core/Mage/Catalog/Block/Product/View.php +++ b/app/code/core/Mage/Catalog/Block/Product/View.php @@ -34,21 +34,29 @@ */ class Mage_Catalog_Block_Product_View extends Mage_Catalog_Block_Product_Abstract { + /** + * Add meta information from product to head block + * + * @return Mage_Catalog_Block_Product_View + */ protected function _prepareLayout() { $this->getLayout()->createBlock('catalog/breadcrumbs'); - if ($headBlock = $this->getLayout()->getBlock('head')) { - if ($title = $this->getProduct()->getMetaTitle()) { + $headBlock = $this->getLayout()->getBlock('head'); + if ($headBlock) { + $title = $this->getProduct()->getMetaTitle(); + if ($title) { $headBlock->setTitle($title); } - - if ($keyword = $this->getProduct()->getMetaKeyword()) { + $keyword = $this->getProduct()->getMetaKeyword(); + $currentCategory = Mage::registry('current_category'); + if ($keyword) { $headBlock->setKeywords($keyword); - } elseif( $currentCategory = Mage::registry('current_category') ) { + } elseif($currentCategory) { $headBlock->setKeywords($this->getProduct()->getName()); } - - if ($description = $this->getProduct()->getMetaDescription()) { + $description = $this->getProduct()->getMetaDescription(); + if ($description) { $headBlock->setDescription( ($description) ); } else { $headBlock->setDescription( $this->getProduct()->getDescription() ); @@ -72,6 +80,11 @@ public function getProduct() return Mage::registry('product'); } + /** + * Check if product can be emailed to friend + * + * @return bool + */ public function canEmailToFriend() { $sendToFriendModel = Mage::registry('send_to_friend_model'); @@ -87,8 +100,6 @@ public function canEmailToFriend() */ public function getAddToCartUrl($product, $additional = array()) { - $additional = array(); - if ($this->getRequest()->getParam('wishlist_next')){ $additional['wishlist_next'] = 1; } @@ -96,9 +107,18 @@ public function getAddToCartUrl($product, $additional = array()) return $this->helper('checkout/cart')->getAddUrl($product, $additional); } + /** + * Get JSON encripted configuration array which can be used for JS dynamic + * price calculation depending on product options + * + * @return string + */ public function getJsonConfig() { $config = array(); + if (!$this->hasOptions()) { + return Mage::helper('core')->jsonEncode($config); + } $_request = Mage::getSingleton('tax/calculation')->getRateRequest(false, false, false); $_request->setProductClassId($this->getProduct()->getTaxClassId()); @@ -113,11 +133,6 @@ public function getJsonConfig() $_priceInclTax = Mage::helper('tax')->getPrice($this->getProduct(), $_finalPrice, true); $_priceExclTax = Mage::helper('tax')->getPrice($this->getProduct(), $_finalPrice); - $idSuffix = '__none__'; - if ($this->hasOptions()) { - $idSuffix = '_clone'; - } - $config = array( 'productId' => $this->getProduct()->getId(), 'priceFormat' => Mage::app()->getLocale()->getJsPriceFormat(), @@ -129,20 +144,20 @@ public function getJsonConfig() 'skipCalculate' => ($_priceExclTax != $_priceInclTax ? 0 : 1), 'defaultTax' => $defaultTax, 'currentTax' => $currentTax, - 'idSuffix' => $idSuffix, + 'idSuffix' => '_clone', 'oldPlusDisposition' => 0, 'plusDisposition' => 0, 'oldMinusDisposition' => 0, 'minusDisposition' => 0, ); - $responseObject = new Varien_Object(); - Mage::dispatchEvent('catalog_product_view_config', array('response_object'=>$responseObject)); - if (is_array($responseObject->getAdditionalOptions())) { - foreach ($responseObject->getAdditionalOptions() as $option=>$value) { - $config[$option] = $value; - } - } + $responseObject = new Varien_Object(); + Mage::dispatchEvent('catalog_product_view_config', array('response_object'=>$responseObject)); + if (is_array($responseObject->getAdditionalOptions())) { + foreach ($responseObject->getAdditionalOptions() as $option=>$value) { + $config[$option] = $value; + } + } return Mage::helper('core')->jsonEncode($config); } diff --git a/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Date.php b/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Date.php index d9b212661e..d6efa96166 100644 --- a/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Date.php +++ b/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Date.php @@ -88,7 +88,7 @@ public function getCalendarDateHtml() ->setId('options_'.$this->getOption()->getId().'_date') ->setName('options['.$this->getOption()->getId().'][date]') ->setClass('product-custom-option datetime-picker input-text' . $require) - ->setImage(Mage::getDesign()->getSkinUrl('images/grid-cal.gif')) + ->setImage($this->getSkinUrl('images/calendar.gif')) ->setExtraParams('onchange="opConfig.reloadPrice()"') ->setFormat(Mage::app()->getLocale()->getDateStrFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT)); diff --git a/app/code/core/Mage/Catalog/Block/Product/View/Type/Configurable.php b/app/code/core/Mage/Catalog/Block/Product/View/Type/Configurable.php index 2e66aaf4fc..7e17934c08 100644 --- a/app/code/core/Mage/Catalog/Block/Product/View/Type/Configurable.php +++ b/app/code/core/Mage/Catalog/Block/Product/View/Type/Configurable.php @@ -167,7 +167,7 @@ public function getJsonConfig() 'basePrice' => $this->_registerJsPrice($this->_convertPrice($this->getProduct()->getFinalPrice())), 'oldPrice' => $this->_registerJsPrice($this->_convertPrice($this->getProduct()->getPrice())), 'productId' => $this->getProduct()->getId(), - 'chooseText' => Mage::helper('catalog')->__('Choose option...'), + 'chooseText' => Mage::helper('catalog')->__('Choose an Option...'), 'taxConfig' => $taxConfig, ); diff --git a/app/code/core/Mage/Catalog/Block/Product/Widget/Link.php b/app/code/core/Mage/Catalog/Block/Product/Widget/Link.php new file mode 100644 index 0000000000..c3427cbb86 --- /dev/null +++ b/app/code/core/Mage/Catalog/Block/Product/Widget/Link.php @@ -0,0 +1,46 @@ + + */ + +class Mage_Catalog_Block_Product_Widget_Link + extends Mage_Catalog_Block_Widget_Link +{ + /** + * Initialize entity model + */ + protected function _construct() + { + parent::_construct(); + $this->_entityResource = Mage::getResourceSingleton('catalog/product'); + } +} diff --git a/app/code/core/Mage/Catalog/Block/Product/Widget/New.php b/app/code/core/Mage/Catalog/Block/Product/Widget/New.php new file mode 100644 index 0000000000..395772896d --- /dev/null +++ b/app/code/core/Mage/Catalog/Block/Product/Widget/New.php @@ -0,0 +1,50 @@ + + */ +class Mage_Catalog_Block_Product_Widget_New + extends Mage_Catalog_Block_Product_New + implements Mage_Cms_Block_Widget_Interface +{ + /** + * Retrieve how much products should be displayed. + * + * @return int + */ + public function getProductsCount() + { + if (!$this->hasData('products_count')) { + return parent::getProductsCount(); + } + return $this->_getData('products_count'); + } +} diff --git a/app/code/core/Mage/Catalog/Block/Widget/Link.php b/app/code/core/Mage/Catalog/Block/Widget/Link.php new file mode 100644 index 0000000000..b6903667d0 --- /dev/null +++ b/app/code/core/Mage/Catalog/Block/Widget/Link.php @@ -0,0 +1,104 @@ + + */ + +class Mage_Catalog_Block_Widget_Link + extends Mage_Core_Block_Html_Link + implements Mage_Cms_Block_Widget_Interface +{ + /** + * Entity model name which must be used to retrieve entity specific data. + * @var null|Mage_Catalog_Model_Resource_Eav_Mysql4_Abstract + */ + protected $_entityResource = null; + + /** + * Prepared href attribute + * + * @var string + */ + protected $_href; + + /** + * Prepared anchor text + * + * @var string + */ + protected $_anchorText; + + /** + * Prepare url using passed id path. + * + * @return string + */ + public function getHref() + { + if (!$this->_href) { + $store = Mage::app()->getStore(); + /* @var $store Mage_Core_Model_Store */ + $href = ""; + if ($this->getData('id_path')) { + $urlRewriteResource = Mage::getResourceSingleton('core/url_rewrite'); + /* @var $urlRewriteResource Mage_Core_Model_Mysql4_Url_Rewrite */ + $href = $urlRewriteResource->getRequestPathByIdPath($this->getData('id_path'), $store); + } + + $this->_href = $store->getUrl('', array('_direct' => $href)); + } + + return $this->_href; + } + + /** + * Prepare anchor text using passed text as parameter. + * If anchor text was not specified get entity name from DB. + * + * @return string + */ + public function getAnchorText() + { + if (!$this->_anchorText && $this->_entityResource) { + if (!$this->getData('anchor_text')) { + $idPath = explode('/', $this->_getData('id_path')); + $id = array_pop($idPath); + if ($id) { + $this->_anchorText = $this->_entityResource->getAttributeRawValue($id, 'name', Mage::app()->getStore()); + } + } else { + $this->_anchorText = $this->getData('anchor_text'); + } + } + + return $this->_anchorText; + } +} diff --git a/app/code/core/Mage/Catalog/Helper/Image.php b/app/code/core/Mage/Catalog/Helper/Image.php index dbdcdd558c..fb77b2e3ed 100644 --- a/app/code/core/Mage/Catalog/Helper/Image.php +++ b/app/code/core/Mage/Catalog/Helper/Image.php @@ -33,13 +33,14 @@ class Mage_Catalog_Helper_Image extends Mage_Core_Helper_Abstract { protected $_model; protected $_scheduleResize = false; - protected $_scheduleWatermark = false; protected $_scheduleRotate = false; protected $_angle; + protected $_watermark; protected $_watermarkPosition; protected $_watermarkSize; protected $_watermarkImageOpacity; + protected $_product; protected $_imageFile; protected $_placeholder; @@ -51,12 +52,12 @@ protected function _reset() { $this->_model = null; $this->_scheduleResize = false; - $this->_scheduleWatermark = false; $this->_scheduleRotate = false; $this->_angle = null; $this->_watermark = null; $this->_watermarkPosition = null; $this->_watermarkSize = null; + $this->_watermarkImageOpacity = null; $this->_product = null; $this->_imageFile = null; return $this; @@ -68,6 +69,12 @@ public function init(Mage_Catalog_Model_Product $product, $attributeName, $image $this->_setModel(Mage::getModel('catalog/product_image')); $this->_getModel()->setDestinationSubdir($attributeName); $this->setProduct($product); + + $this->setWatermark(Mage::getStoreConfig("design/watermark/{$this->_getModel()->getDestinationSubdir()}_image")); + $this->setWatermarkImageOpacity(Mage::getStoreConfig("design/watermark/{$this->_getModel()->getDestinationSubdir()}_imageOpacity")); + $this->setWatermarkPosition(Mage::getStoreConfig("design/watermark/{$this->_getModel()->getDestinationSubdir()}_position")); + $this->setWatermarkSize(Mage::getStoreConfig("design/watermark/{$this->_getModel()->getDestinationSubdir()}_size")); + if ($imageFile) { $this->setImageFile($imageFile); } @@ -94,6 +101,17 @@ public function resize($width, $height = null) return $this; } + /** + * Set image quality, values in percentage from 0 to 100 + * + * @param int $quality + * @return Mage_Catalog_Helper_Image + */ + public function setQuality($quality) + { + $this->_getModel()->setQuality($quality); + return $this; + } /** * Guarantee, that image picture width/height will not be distorted. @@ -188,12 +206,22 @@ public function rotate($angle) return $this; } - public function watermark($fileName, $position, $size=null) + /** + * Add watermark to image + * size param in format 100x200 + * + * @param string $fileName + * @param string $position + * @param string $size + * @param int $imageOpacity + * @return Mage_Catalog_Helper_Image + */ + public function watermark($fileName, $position, $size=null, $imageOpacity=null) { $this->setWatermark($fileName) ->setWatermarkPosition($position) - ->setWatermarkSize($size); - $this->_scheduleWatermark = true; + ->setWatermarkSize($size) + ->setWatermarkImageOpacity($imageOpacity); return $this; } @@ -231,20 +259,8 @@ public function __toString() $this->_getModel()->resize(); } - if( $this->_scheduleWatermark ) { - $this->_getModel() - ->setWatermarkPosition( $this->getWatermarkPosition() ) - ->setWatermarkImageOpacity( $this->getWatermarkImageOpacity() ) - ->setWatermarkSize($this->parseSize($this->getWatermarkSize())) - ->setWatermark($this->getWatermark(), $this->getWatermarkPosition()); - } else { - if( $watermark = Mage::getStoreConfig("design/watermark/{$this->_getModel()->getDestinationSubdir()}_image") ) { - $this->_getModel() - ->setWatermarkPosition( $this->getWatermarkPosition() ) - ->setWatermarkImageOpacity( $this->getWatermarkImageOpacity() ) - ->setWatermarkSize($this->parseSize($this->getWatermarkSize())) - ->setWatermark($watermark, $this->getWatermarkPosition()); - } + if( $this->getWatermark() ) { + $this->_getModel()->setWatermark($this->getWatermark()); } $url = $this->_getModel()->saveFile()->getUrl(); @@ -287,67 +303,103 @@ protected function getAngle() return $this->_angle; } + /** + * Set watermark file name + * + * @param string $watermark + * @return Mage_Catalog_Helper_Image + */ protected function setWatermark($watermark) { $this->_watermark = $watermark; + $this->_getModel()->setWatermarkFile($watermark); return $this; } + /** + * Get watermark file name + * + * @return string + */ protected function getWatermark() { return $this->_watermark; } + /** + * Set watermark position + * + * @param string $position + * @return Mage_Catalog_Helper_Image + */ protected function setWatermarkPosition($position) { $this->_watermarkPosition = $position; + $this->_getModel()->setWatermarkPosition($position); return $this; } + /** + * Get watermark position + * + * @return string + */ protected function getWatermarkPosition() { - if( $this->_watermarkPosition ) { - return $this->_watermarkPosition; - } else { - return Mage::getStoreConfig("design/watermark/{$this->_getModel()->getDestinationSubdir()}_position"); - } + return $this->_watermarkPosition; } + /** + * Set watermark size + * param size in format 100x200 + * + * @param string $size + * @return Mage_Catalog_Helper_Image + */ public function setWatermarkSize($size) { $this->_watermarkSize = $size; + $this->_getModel()->setWatermarkSize($this->parseSize($size)); return $this; } + /** + * Get watermark size + * + * @return string + */ protected function getWatermarkSize() { - if( $this->_watermarkSize ) { - return $this->_watermarkSize; - } else { - return Mage::getStoreConfig("design/watermark/{$this->_getModel()->getDestinationSubdir()}_size"); - } + return $this->_watermarkSize; } + /** + * Set watermark image opacity + * + * @param int $imageOpacity + * @return Mage_Catalog_Helper_Image + */ public function setWatermarkImageOpacity($imageOpacity) { $this->_watermarkImageOpacity = $imageOpacity; + $this->_getModel()->setWatermarkImageOpacity($imageOpacity); return $this; } + /** + * Get watermark image opacity + * + * @return int + */ protected function getWatermarkImageOpacity() { if( $this->_watermarkImageOpacity ) { return $this->_watermarkImageOpacity; } - if ($imageOpacity = Mage::getStoreConfig("design/watermark/{$this->_getModel()->getDestinationSubdir()}_imageOpacity")) - { - return $imageOpacity; - } - return $this->_getModel()->getWatermarkImageOpacity(); } - + protected function setProduct($product) { $this->_product = $product; @@ -400,9 +452,20 @@ public function getOriginalWidth() /** * Retrieve original image height * + * @deprecated * @return int|null */ public function getOriginalHeigh() + { + return $this->getOriginalHeight(); + } + + /** + * Retrieve original image height + * + * @return int|null + */ + public function getOriginalHeight() { return $this->_getModel()->getImageProcessor()->getOriginalHeight(); } @@ -417,7 +480,7 @@ public function getOriginalSizeArray() { return array( $this->getOriginalWidth(), - $this->getOriginalHeigh() + $this->getOriginalHeight() ); } } \ No newline at end of file diff --git a/app/code/core/Mage/Catalog/Helper/Product.php b/app/code/core/Mage/Catalog/Helper/Product.php index bc855bab0d..9937308bb5 100644 --- a/app/code/core/Mage/Catalog/Helper/Product.php +++ b/app/code/core/Mage/Catalog/Helper/Product.php @@ -31,7 +31,8 @@ */ class Mage_Catalog_Helper_Product extends Mage_Core_Helper_Url { - const XML_PATH_PRODUCT_URL_SUFFIX = 'catalog/seo/product_url_suffix'; + const XML_PATH_PRODUCT_URL_SUFFIX = 'catalog/seo/product_url_suffix'; + const XML_PATH_PRODUCT_URL_USE_CATEGORY = 'catalog/seo/product_use_categories'; /** * Cache for product rewrite suffix diff --git a/app/code/core/Mage/Catalog/Helper/Product/Url.php b/app/code/core/Mage/Catalog/Helper/Product/Url.php index 0a4d2a1ba4..d5a02caca3 100644 --- a/app/code/core/Mage/Catalog/Helper/Product/Url.php +++ b/app/code/core/Mage/Catalog/Helper/Product/Url.php @@ -39,19 +39,91 @@ class Mage_Catalog_Helper_Product_Url extends Mage_Core_Helper_Url * * @var array */ - protected $_convertTable; + protected $_convertTable = array( + '&' => 'and', '@' => 'at', '©' => 'c', '®' => 'r', 'À' => 'a', + 'Ã' => 'a', 'Â' => 'a', 'Ä' => 'a', 'Ã…' => 'a', 'Æ' => 'ae','Ç' => 'c', + 'È' => 'e', 'É' => 'e', 'Ë' => 'e', 'ÃŒ' => 'i', 'Ã' => 'i', 'ÃŽ' => 'i', + 'Ã' => 'i', 'Ã’' => 'o', 'Ó' => 'o', 'Ô' => 'o', 'Õ' => 'o', 'Ö' => 'o', + 'Ø' => 'o', 'Ù' => 'u', 'Ú' => 'u', 'Û' => 'u', 'Ãœ' => 'u', 'Ã' => 'y', + 'ß' => 'ss','à ' => 'a', 'á' => 'a', 'â' => 'a', 'ä' => 'a', 'Ã¥' => 'a', + 'æ' => 'ae','ç' => 'c', 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', + 'ì' => 'i', 'Ã' => 'i', 'î' => 'i', 'ï' => 'i', 'ò' => 'o', 'ó' => 'o', + 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ø' => 'o', 'ù' => 'u', 'ú' => 'u', + 'û' => 'u', 'ü' => 'u', 'ý' => 'y', 'þ' => 'p', 'ÿ' => 'y', 'Ä€' => 'a', + 'Ä' => 'a', 'Ä‚' => 'a', 'ă' => 'a', 'Ä„' => 'a', 'Ä…' => 'a', 'Ć' => 'c', + 'ć' => 'c', 'Ĉ' => 'c', 'ĉ' => 'c', 'ÄŠ' => 'c', 'Ä‹' => 'c', 'ÄŒ' => 'c', + 'Ä' => 'c', 'ÄŽ' => 'd', 'Ä' => 'd', 'Ä' => 'd', 'Ä‘' => 'd', 'Ä’' => 'e', + 'Ä“' => 'e', 'Ä”' => 'e', 'Ä•' => 'e', 'Ä–' => 'e', 'Ä—' => 'e', 'Ę' => 'e', + 'Ä™' => 'e', 'Äš' => 'e', 'Ä›' => 'e', 'Äœ' => 'g', 'Ä' => 'g', 'Äž' => 'g', + 'ÄŸ' => 'g', 'Ä ' => 'g', 'Ä¡' => 'g', 'Ä¢' => 'g', 'Ä£' => 'g', 'Ĥ' => 'h', + 'Ä¥' => 'h', 'Ħ' => 'h', 'ħ' => 'h', 'Ĩ' => 'i', 'Ä©' => 'i', 'Ī' => 'i', + 'Ä«' => 'i', 'Ĭ' => 'i', 'Ä' => 'i', 'Ä®' => 'i', 'į' => 'i', 'Ä°' => 'i', + 'ı' => 'i', 'IJ' => 'ij','ij' => 'ij','Ä´' => 'j', 'ĵ' => 'j', 'Ķ' => 'k', + 'Ä·' => 'k', 'ĸ' => 'k', 'Ĺ' => 'l', 'ĺ' => 'l', 'Ä»' => 'l', 'ļ' => 'l', + 'Ľ' => 'l', 'ľ' => 'l', 'Ä¿' => 'l', 'Å€' => 'l', 'Å' => 'l', 'Å‚' => 'l', + 'Ń' => 'n', 'Å„' => 'n', 'Å…' => 'n', 'ņ' => 'n', 'Ň' => 'n', 'ň' => 'n', + 'ʼn' => 'n', 'ÅŠ' => 'n', 'Å‹' => 'n', 'ÅŒ' => 'o', 'Å' => 'o', 'ÅŽ' => 'o', + 'Å' => 'o', 'Å' => 'o', 'Å‘' => 'o', 'Å’' => 'oe','Å“' => 'oe','Å”' => 'r', + 'Å•' => 'r', 'Å–' => 'r', 'Å—' => 'r', 'Ř' => 'r', 'Å™' => 'r', 'Åš' => 's', + 'Å›' => 's', 'Åœ' => 's', 'Å' => 's', 'Åž' => 's', 'ÅŸ' => 's', 'Å ' => 's', + 'Å¡' => 's', 'Å¢' => 't', 'Å£' => 't', 'Ť' => 't', 'Å¥' => 't', 'Ŧ' => 't', + 'ŧ' => 't', 'Ũ' => 'u', 'Å©' => 'u', 'Ū' => 'u', 'Å«' => 'u', 'Ŭ' => 'u', + 'Å' => 'u', 'Å®' => 'u', 'ů' => 'u', 'Å°' => 'u', 'ű' => 'u', 'Ų' => 'u', + 'ų' => 'u', 'Å´' => 'w', 'ŵ' => 'w', 'Ŷ' => 'y', 'Å·' => 'y', 'Ÿ' => 'y', + 'Ź' => 'z', 'ź' => 'z', 'Å»' => 'z', 'ż' => 'z', 'Ž' => 'z', 'ž' => 'z', + 'Å¿' => 'z', 'Æ' => 'e', 'Æ’' => 'f', 'Æ ' => 'o', 'Æ¡' => 'o', 'Ư' => 'u', + 'Æ°' => 'u', 'Ç' => 'a', 'ÇŽ' => 'a', 'Ç' => 'i', 'Ç' => 'i', 'Ç‘' => 'o', + 'Ç’' => 'o', 'Ç“' => 'u', 'Ç”' => 'u', 'Ç•' => 'u', 'Ç–' => 'u', 'Ç—' => 'u', + 'ǘ' => 'u', 'Ç™' => 'u', 'Çš' => 'u', 'Ç›' => 'u', 'Çœ' => 'u', 'Ǻ' => 'a', + 'Ç»' => 'a', 'Ǽ' => 'ae','ǽ' => 'ae','Ǿ' => 'o', 'Ç¿' => 'o', 'É™' => 'e', + 'Ð' => 'jo','Є' => 'e', 'І' => 'i', 'Ї' => 'i', 'Ð' => 'a', 'Б' => 'b', + 'Ð’' => 'v', 'Г' => 'g', 'Д' => 'd', 'Е' => 'e', 'Ж' => 'zh','З' => 'z', + 'И' => 'i', 'Й' => 'j', 'К' => 'k', 'Л' => 'l', 'Ðœ' => 'm', 'Ð' => 'n', + 'О' => 'o', 'П' => 'p', 'Ð ' => 'r', 'С' => 's', 'Т' => 't', 'У' => 'u', + 'Ф' => 'f', 'Ð¥' => 'h', 'Ц' => 'c', 'Ч' => 'ch','Ш' => 'sh','Щ' => 'sch', + 'Ъ' => '-', 'Ы' => 'y', 'Ь' => '-', 'Ð' => 'je','Ю' => 'ju','Я' => 'ja', + 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e', + 'ж' => 'zh','з' => 'z', 'и' => 'i', 'й' => 'j', 'к' => 'k', 'л' => 'l', + 'м' => 'm', 'н' => 'n', 'о' => 'o', 'п' => 'p', 'Ñ€' => 'r', 'Ñ' => 's', + 'Ñ‚' => 't', 'у' => 'u', 'Ñ„' => 'f', 'Ñ…' => 'h', 'ц' => 'c', 'ч' => 'ch', + 'ш' => 'sh','щ' => 'sch','ÑŠ' => '-','Ñ‹' => 'y', 'ÑŒ' => '-', 'Ñ' => 'je', + 'ÑŽ' => 'ju','Ñ' => 'ja','Ñ‘' => 'jo','Ñ”' => 'e', 'Ñ–' => 'i', 'Ñ—' => 'i', + 'Ò' => 'g', 'Ò‘' => 'g', '×' => 'a', 'ב' => 'b', '×’' => 'g', 'ד' => 'd', + '×”' => 'h', 'ו' => 'v', '×–' => 'z', '×—' => 'h', 'ט' => 't', '×™' => 'i', + 'ך' => 'k', '×›' => 'k', 'ל' => 'l', '×' => 'm', 'מ' => 'm', 'ן' => 'n', + '× ' => 'n', 'ס' => 's', '×¢' => 'e', '×£' => 'p', 'פ' => 'p', '×¥' => 'C', + 'צ' => 'c', 'ק' => 'q', 'ר' => 'r', 'ש' => 'w', 'ת' => 't', 'â„¢' => 'tm', + ); - public function getConvertTable() + /** + * Check additional instruction for convertation table in configuration + */ + public function __construct() { - if (is_null($this->_convertTable)) { - $convertNode = Mage::getConfig()->getNode('default/url/convert'); + $convertNode = Mage::getConfig()->getNode('default/url/convert'); + if ($convertNode) { foreach ($convertNode->children() as $node) { $this->_convertTable[strval($node->from)] = strval($node->to); } } + } + + /** + * Get chars convertation table + * + * @return array + */ + public function getConvertTable() + { return $this->_convertTable; } + /** + * Process string based on convertation table + * + * @param string $string + * @return string + */ public function format($string) { return strtr($string, $this->getConvertTable()); diff --git a/app/code/core/Mage/Catalog/Model/Abstract.php b/app/code/core/Mage/Catalog/Model/Abstract.php index 682ddf0431..c0d9ccf1cb 100644 --- a/app/code/core/Mage/Catalog/Model/Abstract.php +++ b/app/code/core/Mage/Catalog/Model/Abstract.php @@ -261,7 +261,7 @@ public function setAttributeDefaultValue($attributeCode, $value) */ public function getAttributeDefaultValue($attributeCode) { - return isset($this->_defaultValues[$attributeCode]) ? $this->_defaultValues[$attributeCode] : null; + return array_key_exists($attributeCode, $this->_defaultValues) ? $this->_defaultValues[$attributeCode] : false; } /** diff --git a/app/code/core/Mage/Catalog/Model/Api/Resource.php b/app/code/core/Mage/Catalog/Model/Api/Resource.php index 9532c6997b..e7789d15e6 100644 --- a/app/code/core/Mage/Catalog/Model/Api/Resource.php +++ b/app/code/core/Mage/Catalog/Model/Api/Resource.php @@ -102,22 +102,29 @@ protected function _getStoreId($store = null) * @param int|string $store * @return Mage_Catalog_Model_Product */ - protected function _getProduct ($productId, $store = null) + protected function _getProduct($productId, $store = null, $identifierType = null) { + $loadByIdOnFalse = false; + if ($identifierType === null) { + $identifierType = 'sku'; + $loadByIdOnFalse = true; + } $product = Mage::getModel('catalog/product'); - + if ($store !== null) { + $product->setStoreId($this->_getStoreId($store)); + } /* @var $product Mage_Catalog_Model_Product */ - - if (is_string($productId)) { + if ($identifierType == 'sku') { $idBySku = $product->getIdBySku($productId); if ($idBySku) { $productId = $idBySku; } + if ($idBySku || $loadByIdOnFalse) { + $product->load($productId); + } + } elseif ($identifierType == 'id') { + $product->load($productId); } - if ($store !== null) { - $product->setStoreId($this->_getStoreId($store)); - } - $product->load($productId); return $product; } diff --git a/app/code/core/Mage/Catalog/Model/Category.php b/app/code/core/Mage/Catalog/Model/Category.php index 912433ad74..b89a0de229 100644 --- a/app/code/core/Mage/Catalog/Model/Category.php +++ b/app/code/core/Mage/Catalog/Model/Category.php @@ -34,6 +34,11 @@ */ class Mage_Catalog_Model_Category extends Mage_Catalog_Model_Abstract { + /** + * Entity code. + * Can be used as part of method name for entity processing + */ + const ENTITY = 'catalog_category'; /** * Category display modes */ @@ -171,15 +176,66 @@ public function getTreeModelInstance() /** * Move category * - * @return Mage_Catalog_Model_Category + * @param int $parentId new parent category id + * @param int $afterCategoryId category id after which we have put current category + * @return Mage_Catalog_Model_Category */ - /* - public function move($parentId) + public function move($parentId, $afterCategoryId) { - $this->getResource()->move($this, $parentId); + /** + * Validate new parent category id. (category model is used for backward + * compatibility in event params) + */ + $parent = Mage::getModel('catalog/category') + ->setStoreId($this->getStoreId()) + ->load($parentId); + + if (!$parent->getId()) { + Mage::throwException( + Mage::helper('catalog')->__('Category move operation is not possible: new parent category not found.') + ); + } + + $eventParams = array( + $this->_eventObject => $this, + 'parent' => $parent, + 'category_id' => $this->getId(), + 'prev_parent_id'=> $this->getParentId(), + 'parent_id' => $parentId + ); + $moveComplete = false; + + $this->_getResource()->beginTransaction(); + try { + /** + * catalog_category_tree_move_before and catalog_category_tree_move_after + * events declared for backward compatibility + */ + Mage::dispatchEvent('catalog_category_tree_move_before', $eventParams); + Mage::dispatchEvent($this->_eventPrefix.'_move_before', $eventParams); + + $this->getResource()->changeParent($this, $parent, $afterCategoryId); + + Mage::dispatchEvent($this->_eventPrefix.'_move_after', $eventParams); + Mage::dispatchEvent('catalog_category_tree_move_after', $eventParams); + $this->_getResource()->commit(); + + $this->setAffectedCategoryIds(array($this->getId(), $this->getParentId(), $parentId)); + $moveComplete = true; + } catch (Exception $e) { + $this->_getResource()->rollBack(); + throw $e; + } + if ($moveComplete) { + Mage::dispatchEvent('category_move', $eventParams); + Mage::getSingleton('index/indexer')->processEntityAction( + $this, self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE + ); + Mage::app()->cleanCache(array(self::CACHE_TAG)); + } + return $this; } - */ /** * Retrieve default attribute set id @@ -844,4 +900,18 @@ public function validate() { return $this->_getResource()->validate($this); } + + /** + * Init indexing process after category data commit + * + * @return Mage_Catalog_Model_Category + */ + protected function _afterSaveCommit() + { + parent::_afterSaveCommit(); + Mage::getSingleton('index/indexer')->processEntityAction( + $this, self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE + ); + return $this; + } } diff --git a/app/code/core/Mage/Catalog/Model/Category/Api.php b/app/code/core/Mage/Catalog/Model/Category/Api.php index 4f0898aea7..99ffdff3ba 100644 --- a/app/code/core/Mage/Catalog/Model/Category/Api.php +++ b/app/code/core/Mage/Catalog/Model/Category/Api.php @@ -392,17 +392,27 @@ public function delete($categoryId) * @param int|string $productId * @return int */ - protected function _getProductId($productId) + protected function _getProductId($productId, $identifierType = null) { + $loadByIdOnFalse = false; + if ($identifierType === null) { + $identifierType = 'sku'; + $loadByIdOnFalse = true; + } $product = Mage::getModel('catalog/product'); - $idBySku = $product->getIdBySku($productId); - if ($idBySku) { - $productId = $idBySku; + if ($identifierType == 'sku') { + $idBySku = $product->getIdBySku($productId); + if ($idBySku) { + $productId = $idBySku; + } + if ($idBySku || $loadByIdOnFalse) { + $product->load($productId); + } + } elseif ($identifierType == 'id') { + $product->load($productId); } - $product->load($productId); - if (!$product->getId()) { $this->_fault('not_exists','Product not exists.'); } @@ -449,7 +459,7 @@ public function assignedProducts($categoryId, $store = null) * @param int $position * @return boolean */ - public function assignProduct($categoryId, $productId, $position = null) + public function assignProduct($categoryId, $productId, $position = null, $identifierType = null) { $category = $this->_initCategory($categoryId); $positions = $category->getProductsPosition(); @@ -475,11 +485,11 @@ public function assignProduct($categoryId, $productId, $position = null) * @param int $position * @return boolean */ - public function updateProduct($categoryId, $productId, $position = null) + public function updateProduct($categoryId, $productId, $position = null, $identifierType = null) { $category = $this->_initCategory($categoryId); $positions = $category->getProductsPosition(); - $productId = $this->_getProductId($productId); + $productId = $this->_getProductId($productId, $identifierType); if (!isset($positions[$productId])) { $this->_fault('product_not_assigned'); } @@ -502,11 +512,11 @@ public function updateProduct($categoryId, $productId, $position = null) * @param int $productId * @return boolean */ - public function removeProduct($categoryId, $productId) + public function removeProduct($categoryId, $productId, $identifierType = null) { $category = $this->_initCategory($categoryId); $positions = $category->getProductsPosition(); - $productId = $this->_getProductId($productId); + $productId = $this->_getProductId($productId, $identifierType); if (!isset($positions[$productId])) { $this->_fault('product_not_assigned'); } diff --git a/app/code/core/Mage/Catalog/Model/Category/Attribute/Backend/Sortby.php b/app/code/core/Mage/Catalog/Model/Category/Attribute/Backend/Sortby.php index dbf258a42e..f4bf32791c 100644 --- a/app/code/core/Mage/Catalog/Model/Category/Attribute/Backend/Sortby.php +++ b/app/code/core/Mage/Catalog/Model/Category/Attribute/Backend/Sortby.php @@ -1,93 +1,96 @@ - - */ -class Mage_Catalog_Model_Category_Attribute_Backend_Sortby - extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract -{ - /** - * Validate process - * - * @param Varien_Object $object - * @return bool - */ - public function validate($object) - { - if (!parent::validate($object)) { - return false; - } - - $attributeCode = $this->getAttribute()->getName(); - if ($attributeCode == 'default_sort_by') { - if ($available = $object->getData('available_sort_by')) { - if (!is_array($available)) { - $available = explode(',', $available); - } - if (!in_array($object->getData($attributeCode), $available)) { - Mage::throwException(Mage::helper('eav')->__('Default Product Listing Sort by not exists on Available Product Listing Sort by')); - } - } - } - - return true; - } - - /** - * Before Attribute Save Process - * - * @param Varien_Object $object - * @return Mage_Catalog_Model_Category_Attribute_Backend_Sortby - */ - public function beforeSave($object) { - $attributeCode = $this->getAttribute()->getName(); - if ($attributeCode == 'available_sort_by') { - $data = $object->getData($attributeCode); - if (!is_array($data)) { - $data = array(); - } - $object->setData($attributeCode, join(',', $data)); - } - return $this; - } - - public function afterLoad($object) { - $attributeCode = $this->getAttribute()->getName(); - if ($attributeCode == 'available_sort_by') { - $data = $object->getData($attributeCode); - if ($data) { - $object->setData($attributeCode, explode(',', $data)); - } - } - return $this; - } -} + + */ +class Mage_Catalog_Model_Category_Attribute_Backend_Sortby + extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract +{ + /** + * Validate process + * + * @param Varien_Object $object + * @return bool + */ + public function validate($object) + { + if (!parent::validate($object)) { + return false; + } + + $attributeCode = $this->getAttribute()->getName(); + if ($attributeCode == 'default_sort_by') { + if ($available = $object->getData('available_sort_by')) { + if (!is_array($available)) { + $available = explode(',', $available); + } + if (!in_array($object->getData($attributeCode), $available)) { + Mage::throwException(Mage::helper('eav')->__('Default Product Listing Sort by not exists on Available Product Listing Sort by')); + } + } + } + + return true; + } + + /** + * Before Attribute Save Process + * + * @param Varien_Object $object + * @return Mage_Catalog_Model_Category_Attribute_Backend_Sortby + */ + public function beforeSave($object) { + $attributeCode = $this->getAttribute()->getName(); + if ($attributeCode == 'available_sort_by') { + $data = $object->getData($attributeCode); + if (!is_array($data)) { + $data = array(); + } + $object->setData($attributeCode, join(',', $data)); + } + if (is_null($object->getData($attributeCode))) { + $object->setData($attributeCode, false); + } + return $this; + } + + public function afterLoad($object) { + $attributeCode = $this->getAttribute()->getName(); + if ($attributeCode == 'available_sort_by') { + $data = $object->getData($attributeCode); + if ($data) { + $object->setData($attributeCode, explode(',', $data)); + } + } + return $this; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Category/Attribute/Backend/Urlkey.php b/app/code/core/Mage/Catalog/Model/Category/Attribute/Backend/Urlkey.php index d4d2c13dc8..6ec0e445e4 100644 --- a/app/code/core/Mage/Catalog/Model/Category/Attribute/Backend/Urlkey.php +++ b/app/code/core/Mage/Catalog/Model/Category/Attribute/Backend/Urlkey.php @@ -46,6 +46,9 @@ public function beforeSave($object) $attributeName = $this->getAttribute()->getName(); $urlKey = $object->getData($attributeName); + if ($urlKey === false) { + return $this; + } if ($urlKey=='') { $urlKey = $object->getName(); } @@ -63,11 +66,14 @@ public function beforeSave($object) public function afterSave($object) { /* @var $object Mage_Catalog_Model_Category */ - if (!$object->getInitialSetupFlag() && $object->getLevel() > 1) { + /** + * Logic moved to Mage_Catalog_Molde_Indexer_Url + */ + /*if (!$object->getInitialSetupFlag() && $object->getLevel() > 1) { if ($object->dataHasChangedFor('url_key') || $object->getIsChangedProductList()) { Mage::getSingleton('catalog/url')->refreshCategoryRewrite($object->getId()); } - } + }*/ } } diff --git a/app/code/core/Mage/Catalog/Model/Category/Attribute/Source/Sortby.php b/app/code/core/Mage/Catalog/Model/Category/Attribute/Source/Sortby.php index 846b24a0b6..58843033ea 100644 --- a/app/code/core/Mage/Catalog/Model/Category/Attribute/Source/Sortby.php +++ b/app/code/core/Mage/Catalog/Model/Category/Attribute/Source/Sortby.php @@ -1,68 +1,68 @@ - - */ -class Mage_Catalog_Model_Category_Attribute_Source_Sortby - extends Mage_Eav_Model_Entity_Attribute_Source_Abstract -{ - /** - * Retrieve Catalog Config Singleton - * - * @return Mage_Catalog_Model_Config - */ - protected function _getCatalogConfig() { - return Mage::getSingleton('catalog/config'); - } - - /** - * Retrieve All options - * - * @return array - */ - public function getAllOptions() - { - if (is_null($this->_options)) { - $this->_options = array(array( - 'label' => Mage::helper('catalog')->__('Best Value'), - 'value' => 'position' - )); - foreach ($this->_getCatalogConfig()->getAttributesUsedForSortBy() as $attribute) { - $this->_options[] = array( - 'label' => Mage::helper('catalog')->__($attribute['frontend_label']), - 'value' => $attribute['attribute_code'] - ); - } - } - return $this->_options; - } -} + + */ +class Mage_Catalog_Model_Category_Attribute_Source_Sortby + extends Mage_Eav_Model_Entity_Attribute_Source_Abstract +{ + /** + * Retrieve Catalog Config Singleton + * + * @return Mage_Catalog_Model_Config + */ + protected function _getCatalogConfig() { + return Mage::getSingleton('catalog/config'); + } + + /** + * Retrieve All options + * + * @return array + */ + public function getAllOptions() + { + if (is_null($this->_options)) { + $this->_options = array(array( + 'label' => Mage::helper('catalog')->__('Best Value'), + 'value' => 'position' + )); + foreach ($this->_getCatalogConfig()->getAttributesUsedForSortBy() as $attribute) { + $this->_options[] = array( + 'label' => Mage::helper('catalog')->__($attribute['frontend_label']), + 'value' => $attribute['attribute_code'] + ); + } + } + return $this->_options; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Category/Indexer/Flat.php b/app/code/core/Mage/Catalog/Model/Category/Indexer/Flat.php new file mode 100644 index 0000000000..b5df8fe484 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Category/Indexer/Flat.php @@ -0,0 +1,224 @@ + + */ +class Mage_Catalog_Model_Category_Indexer_Flat extends Mage_Index_Model_Indexer_Abstract +{ + /** + * Matched entity events + * + * @var array + */ + protected $_matchedEntities = array( + Mage_Catalog_Model_Category::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Core_Model_Store::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE, + Mage_Index_Model_Event::TYPE_DELETE + ), + Mage_Core_Model_Store_Group::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + ); + + /** + * Retrieve Indexer name + * + * @return string + */ + public function getName() + { + return Mage::helper('catalog')->__('Category Flat Data'); + } + + /** + * Retrieve Indexer description + * + * @return string + */ + public function getDescription() + { + return Mage::helper('catalog')->__('Reorganize EAV category structure to flat structure'); + } + + /** + * Retrieve Catalog Category Flat Indexer model + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat + */ + protected function _getIndexer() + { + return Mage::getResourceSingleton('catalog/category_flat'); + } + + /** + * Check if event can be matched by process + * Overwrote for check is flat catalog category is enabled and specific save + * category, store, store_group + * + * @param Mage_Index_Model_Event $event + * @return bool + */ + public function matchEvent(Mage_Index_Model_Event $event) + { + if (!Mage::helper('catalog/category_flat')->isEnabled(true)) { + return false; + } + + $entity = $event->getEntity(); + if ($entity == Mage_Core_Model_Store::ENTITY) { + if ($event->getType() == Mage_Index_Model_Event::TYPE_DELETE) { + return true; + } else if ($event->getType() == Mage_Index_Model_Event::TYPE_SAVE) { + /* @var $store Mage_Core_Model_Store */ + $store = $event->getDataObject(); + if ($store->isObjectNew() || $store->dataHasChangedFor('group_id')) { + return true; + } + return false; + } + } else if ($entity == Mage_Core_Model_Store_Group::ENTITY) { + /* @var $storeGroup Mage_Core_Model_Store_Group */ + $storeGroup = $event->getDataObject(); + if ($storeGroup->dataHasChangedFor('website_id')) { + return true; + } + return false; + } + + return parent::matchEvent($event); + } + + /** + * Register data required by process in event object + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerEvent(Mage_Index_Model_Event $event) + { + switch ($event->getEntity()) { + case Mage_Catalog_Model_Category::ENTITY: + $this->_registerCatalogCategoryEvent($event); + break; + + case Mage_Core_Model_Store::ENTITY: + if ($event->getType() == Mage_Index_Model_Event::TYPE_DELETE) { + $this->_registerCoreStoreEvent($event); + break; + } + case Mage_Core_Model_Store_Group::ENTITY: + $event->addNewData('catalog_category_flat_skip_call_event_handler', true); + $process = $event->getProcess(); + $process->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX); + break; + } + } + + /** + * Register data required by catalog category process in event object + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Category_Indexer_Flat + */ + protected function _registerCatalogCategoryEvent(Mage_Index_Model_Event $event) + { + switch ($event->getType()) { + case Mage_Index_Model_Event::TYPE_SAVE: + /* @var $category Mage_Catalog_Model_Category */ + $category = $event->getDataObject(); + + /** + * Check if category has another affected category ids (category move result) + */ + $affectedCategoryIds = $category->getAffectedCategoryIds(); + if ($affectedCategoryIds) { + $event->addNewData('catalog_category_flat_affected_category_ids', $affectedCategoryIds); + } else { + $event->addNewData('catalog_category_flat_category_id', $category->getId()); + } + + break; + } + return $this; + } + + /** + * Register core store delete process + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Category_Indexer_Flat + */ + protected function _registerCoreStoreEvent(Mage_Index_Model_Event $event) + { + if ($event->getType() == Mage_Index_Model_Event::TYPE_DELETE) { + /* @var $store Mage_Core_Model_Store */ + $store = $event->getDataObject(); + $event->addNewData('catalog_category_flat_delete_store_id', $store->getId()); + } + return $this; + } + +/** + * Process event + * + * @param Mage_Index_Model_Event $event + */ + protected function _processEvent(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + + if (!empty($data['catalog_category_flat_reindex_all'])) { + $this->reindexAll(); + } else if (!empty($data['catalog_category_flat_category_id'])) { + // catalog_product save + $categoryId = $data['catalog_category_flat_category_id']; + $this->_getIndexer()->synchronize($categoryId); + } else if (!empty($data['catalog_category_flat_affected_category_ids'])) { + $categoryIds = $data['catalog_category_flat_affected_category_ids']; + $this->_getIndexer()->move($categoryIds); + } else if (!empty($data['catalog_category_flat_delete_store_id'])) { + $storeId = $data['catalog_category_flat_delete_store_id']; + $this->_getIndexer()->deleteStores($storeId); + } + } + + /** + * Rebuild all index data + * + */ + public function reindexAll() + { + $this->_getIndexer()->rebuild(); + } +} diff --git a/app/code/core/Mage/Catalog/Model/Category/Indexer/Product.php b/app/code/core/Mage/Catalog/Model/Category/Indexer/Product.php new file mode 100644 index 0000000000..7bb234cf38 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Category/Indexer/Product.php @@ -0,0 +1,200 @@ + array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Catalog_Model_Category::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Core_Model_Store::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Core_Model_Store_Group::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Catalog_Model_Convert_Adapter_Product::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ) + ); + + /** + * Initialize resource + */ + protected function _construct() + { + $this->_init('catalog/category_indexer_product'); + } + + /** + * Get Indexer name + * + * @return string + */ + public function getName() + { + return Mage::helper('catalog')->__('Category Products'); + } + + /** + * Get Indexer description + * + * @return string + */ + public function getDescription() + { + return Mage::helper('catalog')->__('Indexed category/products association'); + } + + /** + * Check if event can be matched by process. + * Overwrote for specific config save, store and store groups save matching + * + * @param Mage_Index_Model_Event $event + * @return bool + */ + public function matchEvent(Mage_Index_Model_Event $event) + { + $entity = $event->getEntity(); + if ($entity == Mage_Core_Model_Store::ENTITY) { + $store = $event->getDataObject(); + if ($store->isObjectNew() || $store->dataHasChangedFor('group_id')) { + return true; + } + return false; + } elseif ($entity == Mage_Core_Model_Store_Group::ENTITY) { + $storeGroup = $event->getDataObject(); + $hasDataChanges = $storeGroup->dataHasChangedFor('root_category_id') + || $storeGroup->dataHasChangedFor('website_id'); + if (!$storeGroup->isObjectNew() && $hasDataChanges) { + return true; + } + return false; + } + return parent::matchEvent($event); + } + + + /** + * Register data required by process in event object + * Check if category ids was changed + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerEvent(Mage_Index_Model_Event $event) + { + $entity = $event->getEntity(); + switch ($entity) { + case Mage_Catalog_Model_Product::ENTITY: + $this->_registerProductEvent($event); + break; + + case Mage_Catalog_Model_Category::ENTITY: + $this->_registerCategoryEvent($event); + break; + + case Mage_Catalog_Model_Convert_Adapter_Product::ENTITY: + $event->addNewData('catalog_category_product_reindex_all', true); + break; + + case Mage_Core_Model_Store::ENTITY: + case Mage_Core_Model_Store_Group::ENTITY: + $process = $event->getProcess(); + $process->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX); + break; + } + return $this; + } + + /** + * Register event data during product save process + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerProductEvent(Mage_Index_Model_Event $event) + { + $product = $event->getDataObject(); + /** + * Check if product categories data was changed + */ + if ($product->getIsChangedCategories() || $product->dataHasChangedFor('status') + || $product->dataHasChangedFor('visibility')) { + $event->addNewData('category_ids', $product->getCategoryIds()); + } + } + + /** + * Register event data during category save process + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerCategoryEvent(Mage_Index_Model_Event $event) + { + $category = $event->getDataObject(); + /** + * Check if product categories data was changed + */ + if ($category->getIsChangedProductList()) { + $event->addNewData('products_was_changed', true); + } + /** + * Check if category has another affected category ids (category move result) + */ + if ($category->getAffectedCategoryIds()) { + $event->addNewData('affected_category_ids', $category->getAffectedCategoryIds()); + } + } + + /** + * Process event data and save to index + * + * @param Mage_Index_Model_Event $event + */ + protected function _processEvent(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + if (!empty($data['catalog_category_product_reindex_all'])) { + $this->reindexAll(); + } + if (empty($data['catalog_category_product_skip_call_event_handler'])) { + $this->callEventHandler($event); + } + } +} diff --git a/app/code/core/Mage/Catalog/Model/Config.php b/app/code/core/Mage/Catalog/Model/Config.php index 22503267df..bff44b6afb 100644 --- a/app/code/core/Mage/Catalog/Model/Config.php +++ b/app/code/core/Mage/Catalog/Model/Config.php @@ -58,6 +58,8 @@ class Mage_Catalog_Model_Config extends Mage_Eav_Model_Config */ protected $_usedForSortBy; + protected $_storeId = null; + const XML_PATH_PRODUCT_COLLECTION_ATTRIBUTES = 'frontend/product/collection/attributes'; /** @@ -69,6 +71,31 @@ protected function _construct() $this->_init('catalog/config'); } + /** + * Set store id + * + * @param integer $storeId + * @return Mage_Catalog_Model_Config + */ + public function setStoreId($storeId) + { + $this->_storeId = $storeId; + return $this; + } + + /** + * Return store id, if is not set return current app store + * + * @return integer + */ + public function getStoreId() + { + if ($this->_storeId === null) { + return Mage::app()->getStore()->getId(); + } + return $this->_storeId; + } + public function loadAttributeSets() { if ($this->_attributeSetsById) { @@ -268,6 +295,7 @@ public function getAttributesUsedInProductListing() { $this->_usedInProductListing = array(); $entityType = 'catalog_product'; $attributesData = $this->_getResource() + ->setStoreId($this->getStoreId()) ->getAttributesUsedInListing(); Mage::getSingleton('eav/config') ->importAttributesData($entityType, $attributesData); @@ -315,7 +343,7 @@ public function getAttributeUsedForSortByArray() ); foreach ($this->getAttributesUsedForSortBy() as $attribute) { /* @var $attribute Mage_Eav_Model_Entity_Attribute_Abstract */ - $options[$attribute->getAttributeCode()] = Mage::helper('catalog')->__($attribute->getFrontendLabel()); + $options[$attribute->getAttributeCode()] = $attribute->getStoreLabel(); } return $options; diff --git a/app/code/core/Mage/Catalog/Model/Convert/Adapter/Product.php b/app/code/core/Mage/Catalog/Model/Convert/Adapter/Product.php index b79d1182b2..d4ddefed43 100644 --- a/app/code/core/Mage/Catalog/Model/Convert/Adapter/Product.php +++ b/app/code/core/Mage/Catalog/Model/Convert/Adapter/Product.php @@ -28,7 +28,8 @@ class Mage_Catalog_Model_Convert_Adapter_Product extends Mage_Eav_Model_Convert_Adapter_Entity { - const MULTI_DELIMITER = ' , '; + const MULTI_DELIMITER = ' , '; + const ENTITY = 'catalog_product_import'; /** * Product model @@ -648,9 +649,8 @@ public function saveRow(array $importData) $setValue[] = $item['value']; } } - } - else { - $setValue = null; + } else { + $setValue = false; foreach ($options as $item) { if ($item['label'] == $value) { $setValue = $item['value']; @@ -726,10 +726,19 @@ public function saveRowSilently(array $importData) /** * Process after import data + * Init indexing process after catalog product import * */ public function finish() { + /** + * Back compatibility event + */ Mage::dispatchEvent('catalog_product_import_after', array()); + + $entity = new Varien_Object(); + Mage::getSingleton('index/indexer')->processEntityAction( + $entity, self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE + ); } } diff --git a/app/code/core/Mage/Catalog/Model/Convert/Parser/Product.php b/app/code/core/Mage/Catalog/Model/Convert/Parser/Product.php index d4e472a2bf..92966eed2b 100644 --- a/app/code/core/Mage/Catalog/Model/Convert/Parser/Product.php +++ b/app/code/core/Mage/Catalog/Model/Convert/Parser/Product.php @@ -486,12 +486,9 @@ public function unparse() public function getExternalAttributes() { $entityTypeId = Mage::getSingleton('eav/config')->getEntityType('catalog_product')->getId(); - $productAttributes = Mage::getResourceModel('eav/entity_attribute_collection') - ->setEntityTypeFilter($entityTypeId) + $productAttributes = Mage::getResourceModel('catalog/product_attribute_collection') ->load(); - var_dump($this->_externalFields); - $attributes = $this->_externalFields; foreach ($productAttributes as $attr) { @@ -508,4 +505,4 @@ public function getExternalAttributes() return $attributes; } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Catalog/Model/Indexer/Url.php b/app/code/core/Mage/Catalog/Model/Indexer/Url.php new file mode 100644 index 0000000000..fac0e402aa --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Indexer/Url.php @@ -0,0 +1,226 @@ + array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Catalog_Model_Category::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Core_Model_Store::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Core_Model_Store_Group::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Core_Model_Config_Data::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Catalog_Model_Convert_Adapter_Product::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ) + ); + + protected $_relatedConfigSettings = array( + Mage_Catalog_Helper_Category::XML_PATH_CATEGORY_URL_SUFFIX, + Mage_Catalog_Helper_Product::XML_PATH_PRODUCT_URL_SUFFIX, + Mage_Catalog_Helper_Product::XML_PATH_PRODUCT_URL_USE_CATEGORY, + ); + + /** + * Get Indexer name + * + * @return string + */ + public function getName() + { + return Mage::helper('catalog')->__('Catalog Url Rewrites'); + } + + /** + * Get Indexer description + * + * @return string + */ + public function getDescription() + { + return Mage::helper('catalog')->__('Index product and categories url rewrites'); + } + + /** + * Check if event can be matched by process. + * Overwrote for specific config save, store and store groups save matching + * + * @param Mage_Index_Model_Event $event + * @return bool + */ + public function matchEvent(Mage_Index_Model_Event $event) + { + $entity = $event->getEntity(); + if ($entity == Mage_Core_Model_Store::ENTITY) { + $store = $event->getDataObject(); + if ($store->isObjectNew() || $store->dataHasChangedFor('group_id')) { + return true; + } + return false; + } elseif ($entity == Mage_Core_Model_Store_Group::ENTITY) { + $storeGroup = $event->getDataObject(); + $hasDataChanges = $storeGroup->dataHasChangedFor('root_category_id') + || $storeGroup->dataHasChangedFor('website_id'); + if (!$storeGroup->isObjectNew() && $hasDataChanges) { + return true; + } + return false; + } elseif ($entity == Mage_Core_Model_Config_Data::ENTITY) { + $configData = $event->getDataObject(); + $path = $configData->getPath(); + if (in_array($path, $this->_relatedConfigSettings)) { + return $configData->isValueChanged(); + } + return false; + } + return parent::matchEvent($event); + } + + /** + * Register data required by process in event object + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerEvent(Mage_Index_Model_Event $event) + { + $entity = $event->getEntity(); + switch ($entity) { + case Mage_Catalog_Model_Product::ENTITY: + $this->_registerProductEvent($event); + break; + + case Mage_Catalog_Model_Category::ENTITY: + $this->_registerCategoryEvent($event); + break; + + case Mage_Catalog_Model_Convert_Adapter_Product::ENTITY: + $event->addNewData('catalog_url_reindex_all', true); + break; + + case Mage_Core_Model_Store::ENTITY: + case Mage_Core_Model_Store_Group::ENTITY: + case Mage_Core_Model_Config_Data::ENTITY: + $process = $event->getProcess(); + $process->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX); + break; + } + return $this; + } + + /** + * Register event data during product save process + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerProductEvent(Mage_Index_Model_Event $event) + { + $product = $event->getDataObject(); + $dataChange = $product->dataHasChangedFor('url_key') + || $product->getIsChangedCategories() + || $product->getIsChangedWebsites(); + + if (!$product->getExcludeUrlRewrite() && $dataChange) { + $event->addNewData('rewrite_product_ids', array($product->getId())); + } + } + + /** + * Register event data during category save process + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerCategoryEvent(Mage_Index_Model_Event $event) + { + $category = $event->getDataObject(); + if (!$category->getInitialSetupFlag() && $category->getLevel() > 1) { + if ($category->dataHasChangedFor('url_key') || $category->getIsChangedProductList()) { + $event->addNewData('rewrite_category_ids', array($category->getId())); + } + /** + * Check if category has another affected category ids (category move result) + */ + if ($category->getAffectedCategoryIds()) { + $event->addNewData('rewrite_category_ids', $category->getAffectedCategoryIds()); + } + } + } + + /** + * Process event + * + * @param Mage_Index_Model_Event $event + */ + protected function _processEvent(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + + if (!empty($data['catalog_url_reindex_all'])) { + $this->reindexAll(); + } + if(isset($data['rewrite_product_ids'])) { + foreach ($data['rewrite_product_ids'] as $productId) { + Mage::getSingleton('catalog/url')->refreshProductRewrite($productId); + } + } + if (isset($data['rewrite_category_ids'])) { + foreach ($data['rewrite_category_ids'] as $categoryId) { + Mage::getSingleton('catalog/url')->refreshCategoryRewrite($categoryId); + } + } + } + + /** + * Rebuild all index data + */ + public function reindexAll() + { + Mage::getSingleton('catalog/url')->refreshRewrites(); + } +} diff --git a/app/code/core/Mage/Catalog/Model/Layer.php b/app/code/core/Mage/Catalog/Model/Layer.php index 8c567e7e82..7c39b4bb1f 100644 --- a/app/code/core/Mage/Catalog/Model/Layer.php +++ b/app/code/core/Mage/Catalog/Model/Layer.php @@ -122,7 +122,6 @@ public function prepareProductCollection($collection) ->addTaxPercents() //->addStoreFilter() ; - Mage::getSingleton('catalog/product_status')->addVisibleFilterToCollection($collection); Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($collection); $collection->addUrlRewrite($this->getCurrentCategory()->getId()); @@ -210,27 +209,25 @@ public function getCurrentStore() /** * Get collection of all filterable attributes for layer products set * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Attribute_Collection */ public function getFilterableAttributes() { - $entity = Mage::getSingleton('eav/config') - ->getEntityType('catalog_product'); +// $entity = Mage::getSingleton('eav/config') +// ->getEntityType('catalog_product'); $setIds = $this->_getSetIds(); if (!$setIds) { return array(); } - - $collection = Mage::getModel('eav/entity_attribute') - ->getCollection() + /* @var $collection Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Collection */ + $collection = Mage::getResourceModel('catalog/product_attribute_collection') ->setItemObjectClass('catalog/resource_eav_attribute'); - /* @var $collection Mage_Eav_Model_Mysql4_Entity_Attribute_Collection */ $collection->getSelect()->distinct(true); $collection - ->setEntityTypeFilter($entity->getId()) ->setAttributeSetFilter($setIds) + ->addStoreLabel(Mage::app()->getStore()->getId()) ->setOrder('position', 'ASC'); $collection = $this->_prepareAttributeCollection($collection); $collection->load(); @@ -253,8 +250,8 @@ protected function _prepareAttribute($attribute) /** * Add filters to attribute collection * - * @param Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection + * @param Mage_Catalog_Model_Resource_Eav_Mysql4_Attribute_Collection $collection + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Attribute_Collection */ protected function _prepareAttributeCollection($collection) { diff --git a/app/code/core/Mage/Catalog/Model/Layer/Filter/Abstract.php b/app/code/core/Mage/Catalog/Model/Layer/Filter/Abstract.php index 5ffebf2fd7..e877b1a1f8 100644 --- a/app/code/core/Mage/Catalog/Model/Layer/Filter/Abstract.php +++ b/app/code/core/Mage/Catalog/Model/Layer/Filter/Abstract.php @@ -230,7 +230,7 @@ public function setAttributeModel($attribute) /** * Get attribute model associated with filter * - * @return Mage_Eav_Model_Entity_Attribute + * @return Mage_Catalog_Model_Resource_Eav_Attribute */ public function getAttributeModel() { @@ -248,6 +248,56 @@ public function getAttributeModel() */ public function getName() { - return $this->getAttributeModel()->getFrontendLabel(); + return $this->getAttributeModel()->getStoreLabel(); + } + + /** + * Retrieve current store id scope + * + * @return int + */ + public function getStoreId() + { + $storeId = $this->_getData('store_id'); + if (is_null($storeId)) { + $storeId = Mage::app()->getStore()->getId(); + } + return $storeId; + } + + /** + * Set store id scope + * + * @param int $storeId + * @return Mage_Catalog_Model_Layer_Filter_Abstract + */ + public function setStoreId($storeId) + { + return $this->setData('store_id', $storeId); + } + + /** + * Retrieve Website ID scope + * + * @return int + */ + public function getWebsiteId() + { + $websiteId = $this->_getData('website_id'); + if (is_null($websiteId)) { + $websiteId = Mage::app()->getStore()->getWebsiteId(); + } + return $websiteId; + } + + /** + * Set Website ID scope + * + * @param int $websiteId + * @return Mage_Catalog_Model_Layer_Filter_Abstract + */ + public function setWebsiteId($websiteId) + { + return $this->setData('website_id', $websiteId); } } diff --git a/app/code/core/Mage/Catalog/Model/Layer/Filter/Attribute.php b/app/code/core/Mage/Catalog/Model/Layer/Filter/Attribute.php index 0bed8286ac..f692b6ffce 100644 --- a/app/code/core/Mage/Catalog/Model/Layer/Filter/Attribute.php +++ b/app/code/core/Mage/Catalog/Model/Layer/Filter/Attribute.php @@ -35,6 +35,13 @@ class Mage_Catalog_Model_Layer_Filter_Attribute extends Mage_Catalog_Model_Layer { const OPTIONS_ONLY_WITH_RESULTS = 1; + /** + * Resource instance + * + * @var Mage_Catalog_Model_Resource_Eav_Mysql4_Layer_Filter_Attribute + */ + protected $_resource; + /** * Construct attribute filter * @@ -45,6 +52,19 @@ public function __construct() $this->_requestVar = 'attribute'; } + /** + * Retrieve resource instance + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Layer_Filter_Attribute + */ + protected function _getResource() + { + if (is_null($this->_resource)) { + $this->_resource = Mage::getResourceModel('catalog/layer_filter_attribute'); + } + return $this->_resource; + } + /** * Get option text from frontend model by option id * @@ -71,11 +91,7 @@ public function apply(Zend_Controller_Request_Abstract $request, $filterBlock) } $text = $this->_getOptionText($filter); if ($filter && $text) { - Mage::getSingleton('catalogindex/attribute')->applyFilterToCollection( - $this->getLayer()->getProductCollection(), - $this->getAttributeModel(), - $filter - ); + $this->_getResource()->applyFilterToCollection($this, $filter); $this->getLayer()->getState()->addFilter($this->_createItem($text, $filter)); $this->_items = array(); } @@ -108,10 +124,7 @@ protected function _getItemsData() if ($data === null) { $options = $attribute->getFrontend()->getSelectOptions(); - $optionsCount = Mage::getSingleton('catalogindex/attribute')->getCount( - $attribute, - $this->_getBaseCollectionSql() - ); + $optionsCount = $this->_getResource()->getCount($this); $data = array(); foreach ($options as $option) { diff --git a/app/code/core/Mage/Catalog/Model/Layer/Filter/Price.php b/app/code/core/Mage/Catalog/Model/Layer/Filter/Price.php index 9b91a8c5eb..becc7fc1d2 100644 --- a/app/code/core/Mage/Catalog/Model/Layer/Filter/Price.php +++ b/app/code/core/Mage/Catalog/Model/Layer/Filter/Price.php @@ -35,6 +35,13 @@ class Mage_Catalog_Model_Layer_Filter_Price extends Mage_Catalog_Model_Layer_Fil { const MIN_RANGE_POWER = 10; + /** + * Resource instance + * + * @var Mage_Catalog_Model_Resource_Eav_Mysql4_Layer_Filter_Price + */ + protected $_resource; + /** * Class constructor * @@ -45,6 +52,19 @@ public function __construct() $this->_requestVar = 'price'; } + /** + * Retrieve resource instance + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Layer_Filter_Price + */ + protected function _getResource() + { + if (is_null($this->_resource)) { + $this->_resource = Mage::getResourceModel('catalog/layer_filter_price'); + } + return $this->_resource; + } + /** * Get price range for building filter steps * @@ -77,11 +97,7 @@ public function getMaxPriceInt() { $maxPrice = $this->getData('max_price_int'); if (is_null($maxPrice)) { - $maxPrice = Mage::getSingleton('catalogindex/price')->getMaxValue( - $this->getAttributeModel(), - $this->_getBaseCollectionSql() - ); - + $maxPrice = $this->_getResource()->getMaxPrice($this); $maxPrice = floor($maxPrice); $this->setData('max_price_int', $maxPrice); } @@ -96,14 +112,11 @@ public function getMaxPriceInt() */ public function getRangeItemCounts($range) { - $items = $this->getData('range_item_counts_'.$range); + $rangeKey = 'range_item_counts_' . $range; + $items = $this->getData($rangeKey); if (is_null($items)) { - $items = Mage::getSingleton('catalogindex/price')->getCount( - $this->getAttributeModel(), - $range, - $this->_getBaseCollectionSql() - ); - $this->setData('range_item_counts_'.$range, $items); + $items = $this->_getResource()->getCount($this, $range); + $this->setData($rangeKey, $items); } return $items; } @@ -198,13 +211,7 @@ public function apply(Zend_Controller_Request_Abstract $request, $filterBlock) if ((int)$index && (int)$range) { $this->setPriceRange((int)$range); - Mage::getSingleton('catalogindex/price')->applyFilterToCollection( - $this->getLayer()->getProductCollection(), - $this->getAttributeModel(), - $range, - $index - ); - + $this->_getResource()->applyFilterToCollection($this, $range, $index); $this->getLayer()->getState()->addFilter( $this->_createItem($this->_renderItemLabel($range, $index), $filter) ); @@ -213,4 +220,57 @@ public function apply(Zend_Controller_Request_Abstract $request, $filterBlock) } return $this; } + + /** + * Retrieve active customer group id + * + * @return int + */ + public function getCustomerGroupId() + { + $customerGroupId = $this->_getData('customer_group_id'); + if (is_null($customerGroupId)) { + $customerGroupId = Mage::getSingleton('customer/session')->getCustomerGroupId(); + } + return $customerGroupId; + } + + /** + * Set active customer group id for filter + * + * @param int $customerGroupId + * @return Mage_Catalog_Model_Layer_Filter_Price + */ + public function setCustomerGroupId($customerGroupId) + { + return $this->setData('customer_group_id', $customerGroupId); + } + + /** + * Retrieve active currency rate for filter + * + * @return float + */ + public function getCurrencyRate() + { + $rate = $this->_getData('currency_rate'); + if (is_null($rate)) { + $rate = Mage::app()->getStore($this->getStoreId())->getCurrentCurrencyRate(); + } + if (!$rate) { + $rate = 1; + } + return $rate; + } + + /** + * Set active currency rate for filter + * + * @param float $rate + * @return Mage_Catalog_Model_Layer_Filter_Price + */ + public function setCurrencyRate($rate) + { + return $this->setData('currency_rate', $rate); + } } diff --git a/app/code/core/Mage/Catalog/Model/Observer.php b/app/code/core/Mage/Catalog/Model/Observer.php index b928f47184..e952e311e7 100644 --- a/app/code/core/Mage/Catalog/Model/Observer.php +++ b/app/code/core/Mage/Catalog/Model/Observer.php @@ -46,12 +46,19 @@ public function storeEdit(Varien_Event_Observer $observer) /* @var $store Mage_Core_Model_Store */ if ($store->dataHasChangedFor('group_id')) { Mage::app()->reinitStores(); - Mage::getModel('catalog/url')->refreshRewrites($store->getId()); - Mage::getResourceModel('catalog/category')->refreshProductIndex( + /** + * @see Mage_Catalog_Model_Indexer_Url + */ + //Mage::getModel('catalog/url')->refreshRewrites($store->getId()); + + /** + * @see Mage_Catalog_Model_Category_Indexer_Product + */ + /*Mage::getResourceModel('catalog/category')->refreshProductIndex( array(), array(), array($store->getId()) - ); + );*/ if (Mage::helper('catalog/category_flat')->isEnabled(true)) { Mage::getResourceModel('catalog/category_flat')->synchronize(null, array($store->getId())); } @@ -72,12 +79,19 @@ public function storeAdd(Varien_Event_Observer $observer) /* @var $store Mage_Core_Model_Store */ Mage::app()->reinitStores(); Mage::getConfig()->reinit(); - Mage::getModel('catalog/url')->refreshRewrites($store->getId()); - Mage::getResourceSingleton('catalog/category')->refreshProductIndex( + + /** + * @see Mage_Catalog_Model_Indexer_Url + */ + //Mage::getModel('catalog/url')->refreshRewrites($store->getId()); + /** + * @see Mage_Catalog_Model_Category_Indexer_Product + */ + /*Mage::getResourceSingleton('catalog/category')->refreshProductIndex( array(), array(), array($store->getId()) - ); + );*/ if (Mage::helper('catalog/category_flat')->isEnabled(true)) { Mage::getResourceModel('catalog/category_flat') ->synchronize(null, array($store->getId())); @@ -99,12 +113,18 @@ public function storeGroupSave(Varien_Event_Observer $observer) if ($group->dataHasChangedFor('root_category_id') || $group->dataHasChangedFor('website_id')) { Mage::app()->reinitStores(); foreach ($group->getStores() as $store) { - Mage::getModel('catalog/url')->refreshRewrites($store->getId()); - Mage::getResourceSingleton('catalog/category')->refreshProductIndex( + /** + * @see Mage_Catalog_Model_Indexer_Url + */ + //Mage::getModel('catalog/url')->refreshRewrites($store->getId()); + /** + * @see Mage_Catalog_Model_Category_Indexer_Product + */ + /*Mage::getResourceSingleton('catalog/category')->refreshProductIndex( array(), array(), array($store->getId()) - ); + );*/ if (Mage::helper('catalog/category_flat')->isEnabled(true)) { Mage::getResourceModel('catalog/category_flat') ->synchronize(null, array($store->getId())); @@ -140,12 +160,18 @@ public function categoryMove(Varien_Event_Observer $observer) $categoryId = $observer->getEvent()->getCategoryId(); $prevParentId = $observer->getEvent()->getPrevParentId(); $parentId = $observer->getEvent()->getParentId(); - Mage::getModel('catalog/url')->refreshCategoryRewrite($categoryId); - Mage::getResourceSingleton('catalog/category')->refreshProductIndex(array( + /** + * @see Mage_Catalog_Model_Indexer_Url + */ + //Mage::getModel('catalog/url')->refreshCategoryRewrite($categoryId); + /** + * @see Mage_Catalog_Model_Category_Indexer_Product + */ + /*Mage::getResourceSingleton('catalog/category')->refreshProductIndex(array( $categoryId, $prevParentId, $parentId - )); - Mage::getModel('catalog/category')->load($prevParentId)->save(); - Mage::getModel('catalog/category')->load($parentId)->save(); + ));*/ + //Mage::getModel('catalog/category')->load($prevParentId)->save(); + //Mage::getModel('catalog/category')->load($parentId)->save(); if (Mage::helper('catalog/category_flat')->isEnabled(true)) { Mage::getResourceModel('catalog/category_flat') ->move($categoryId, $prevParentId, $parentId); diff --git a/app/code/core/Mage/Catalog/Model/Product.php b/app/code/core/Mage/Catalog/Model/Product.php index dabf92d06f..5c82d5f41f 100644 --- a/app/code/core/Mage/Catalog/Model/Product.php +++ b/app/code/core/Mage/Catalog/Model/Product.php @@ -33,6 +33,12 @@ */ class Mage_Catalog_Model_Product extends Mage_Catalog_Model_Abstract { + /** + * Entity code. + * Can be used as part of method name for entity processing + */ + const ENTITY = 'catalog_product'; + const CACHE_TAG = 'catalog_product'; protected $_cacheTag = 'catalog_product'; protected $_eventPrefix = 'catalog_product'; @@ -429,6 +435,7 @@ protected function _beforeSave() if ($this->getCanSaveCustomOptions()) { $options = $this->getProductOptions(); if (is_array($options)) { + $this->setIsCustomOptionChanged(); foreach ($this->getProductOptions() as $option) { $this->getOptionInstance()->addOption($option); if ((!isset($option['is_delete'])) || $option['is_delete'] != '1') { @@ -436,8 +443,8 @@ protected function _beforeSave() } } foreach ($this->getOptionInstance()->getOptions() as $option) { - if ($option['is_require'] == '1') { - $hasRequiredOptions = true; + if ($option['is_require'] == '1') { + $hasRequiredOptions = true; break; } } @@ -494,12 +501,26 @@ protected function _afterSave() */ $this->getOptionInstance()->setProduct($this) ->saveOptions(); + return parent::_afterSave(); + } - parent::_afterSave(); + /** + * Init indexing process after product data commit + * + * @return Mage_Catalog_Model_Product + */ + protected function _afterSaveCommit() + { + parent::_afterSaveCommit(); + Mage::getSingleton('index/indexer')->processEntityAction( + $this, self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE + ); + return $this; } /** * Clear chache related with product and protect delete from not admin + * Register indexing event before delete product * * @return Mage_Catalog_Model_Product */ @@ -507,9 +528,25 @@ protected function _beforeDelete() { $this->cleanCache(); $this->_protectFromNonAdmin(); + Mage::getSingleton('index/indexer')->logEvent( + $this, self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE + ); return parent::_beforeDelete(); } + /** + * Init indexing process after product delete commit + * + * @return Mage_Catalog_Model_Product + */ + protected function _afterDeleteCommit() + { + parent::_afterDeleteCommit(); + Mage::getSingleton('index/indexer')->indexEvents( + self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE + ); + } + /** * Load product options if they exists * diff --git a/app/code/core/Mage/Catalog/Model/Product/Action.php b/app/code/core/Mage/Catalog/Model/Product/Action.php new file mode 100644 index 0000000000..c6630f6aad --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Product/Action.php @@ -0,0 +1,123 @@ + + */ +class Mage_Catalog_Model_Product_Action extends Mage_Core_Model_Abstract +{ + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('catalog/product_action'); + } + + /** + * Retrieve resource instance wrapper + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Action + */ + protected function _getResource() + { + return parent::_getResource(); + } + + /** + * Update attribute values for entity list per store + * + * @param array $productIds + * @param array $attrData + * @param int $storeId + * @return Mage_Catalog_Model_Product_Action + */ + public function updateAttributes($productIds, $attrData, $storeId) + { + $this->_getResource()->updateAttributes($productIds, $attrData, $storeId); + $this->setData(array( + 'product_ids' => array_unique($productIds), + 'attributes_data' => $attrData, + 'store_id' => $storeId + )); + + // register mass action indexer event + Mage::getSingleton('index/indexer')->processEntityAction( + $this, Mage_Catalog_Model_Product::ENTITY, Mage_Index_Model_Event::TYPE_MASS_ACTION + ); + return $this; + } + + /** + * Update websites for product action + * + * allowed types: + * - add + * - remove + * + * @param array $productIds + * @param array $websiteIds + * @param string $type + */ + public function updateWebsites($productIds, $websiteIds, $type) + { + Mage::dispatchEvent('catalog_product_website_update_before', array( + 'website_ids' => $websiteIds, + 'product_ids' => $productIds, + 'action' => $type + )); + + if ($type == 'add') { + Mage::getModel('catalog/product_website')->addProducts($websiteIds, $productIds); + } else if ($type == 'remove') { + Mage::getModel('catalog/product_website')->removeProducts($websiteIds, $productIds); + } + + $this->setData(array( + 'product_ids' => array_unique($productIds), + 'website_ids' => $websiteIds, + 'action_type' => $type + )); + + // register mass action indexer event + Mage::getSingleton('index/indexer')->processEntityAction( + $this, Mage_Catalog_Model_Product::ENTITY, Mage_Index_Model_Event::TYPE_MASS_ACTION + ); + + // add back compatibility system event + Mage::dispatchEvent('catalog_product_website_update', array( + 'website_ids' => $websiteIds, + 'product_ids' => $productIds, + 'action' => $type + )); + } +} diff --git a/app/code/core/Mage/Catalog/Model/Product/Api.php b/app/code/core/Mage/Catalog/Model/Product/Api.php index a29ab2b7e6..b6a6e1725c 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Api.php +++ b/app/code/core/Mage/Catalog/Model/Product/Api.php @@ -98,9 +98,9 @@ public function items($filters = null, $store = null) * @param array $attributes * @return array */ - public function info($productId, $store = null, $attributes = null) + public function info($productId, $store = null, $attributes = null, $identifierType = null) { - $product = $this->_getProduct($productId, $store); + $product = $this->_getProduct($productId, $store, $identifierType); if (!$product->getId()) { $this->_fault('not_exists'); @@ -183,9 +183,9 @@ public function create($type, $set, $sku, $productData) * @param string|int $store * @return boolean */ - public function update($productId, $productData, $store = null) + public function update($productId, $productData, $store = null, $identifierType = null) { - $product = $this->_getProduct($productId, $store); + $product = $this->_getProduct($productId, $store, $identifierType); if (!$product->getId()) { $this->_fault('not_exists'); @@ -294,9 +294,9 @@ public function getSpecialPrice($productId, $store = null) * @param int|string $productId * @return boolean */ - public function delete($productId) + public function delete($productId, $identifierType = null) { - $product = $this->_getProduct($productId); + $product = $this->_getProduct($productId, null, $identifierType); if (!$product->getId()) { $this->_fault('not_exists'); diff --git a/app/code/core/Mage/Catalog/Model/Product/Api/V2.php b/app/code/core/Mage/Catalog/Model/Product/Api/V2.php index d153de5916..5d11a8d6db 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Api/V2.php +++ b/app/code/core/Mage/Catalog/Model/Product/Api/V2.php @@ -101,9 +101,9 @@ public function items($filters = null, $store = null) * @param stdClass $attributes * @return array */ - public function info($productId, $store = null, $attributes = null) + public function info($productId, $store = null, $attributes = null, $identifierType = null) { - $product = $this->_getProduct($productId, $store); + $product = $this->_getProduct($productId, $store, $identifierType); if (!$product->getId()) { $this->_fault('not_exists'); @@ -214,9 +214,9 @@ public function create($type, $set, $sku, $productData) * @param string|int $store * @return boolean */ - public function update($productId, $productData, $store = null) + public function update($productId, $productData, $store = null, $identifierType = null) { - $product = $this->_getProduct($productId, $store); + $product = $this->_getProduct($productId, $store, $identifierType); if (!$product->getId()) { $this->_fault('not_exists'); diff --git a/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Media.php b/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Media.php index 08e5d41bdd..170b49e178 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Media.php +++ b/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Media.php @@ -123,7 +123,7 @@ public function beforeSave($object) $attrData = $object->getData($mediaAttrCode); if (in_array($attrData, $clearImages)) { - $object->setData($mediaAttrCode, null); + $object->setData($mediaAttrCode, false); } if (in_array($attrData, array_keys($newImages))) { diff --git a/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Tierprice.php b/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Tierprice.php index 3be9195005..2bb2228281 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Tierprice.php +++ b/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Tierprice.php @@ -42,7 +42,7 @@ class Mage_Catalog_Model_Product_Attribute_Backend_Tierprice extends Mage_Catalo protected $_rates; /** - * Retrieve resource model + * Retrieve resource instance * * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Backend_Tierprice */ @@ -74,8 +74,7 @@ public function _getWebsiteRates() 'code' => $website->getBaseCurrencyCode(), 'rate' => $rate ); - } - else { + } else { $this->_rates[$website->getId()] = array( 'code' => $baseCurrency, 'rate' => 1 @@ -87,16 +86,18 @@ public function _getWebsiteRates() } /** - * Validate data + * Validate tier price data * - * @param Mage_Catalog_Model_Product $object - * @return this + * @param Mage_Catalog_Model_Product $object + * @throws Mage_Core_Exception + * @return bool */ public function validate($object) { - $tiers = $object->getData($this->getAttribute()->getName()); + $attribute = $this->getAttribute(); + $tiers = $object->getData($attribute->getName()); if (empty($tiers)) { - return $this; + return true; } // validate per website @@ -105,7 +106,7 @@ public function validate($object) if (!empty($tier['delete'])) { continue; } - $compare = join('-', array($tier['website_id'], $tier['cust_group'], $tier['price_qty'])); + $compare = join('-', array($tier['website_id'], $tier['cust_group'], $tier['price_qty'] * 1)); if (isset($duplicates[$compare])) { Mage::throwException( Mage::helper('catalog')->__('Duplicate website tier price customer group and quantity.') @@ -114,6 +115,18 @@ public function validate($object) $duplicates[$compare] = true; } + // if attribute scope is website and edit in store view scope + // add global tier prices for duplicates find + if (!$attribute->isScopeGlobal() && $object->getStoreId()) { + $origTierPrices = $object->getOrigData($attribute->getName()); + foreach ($origTierPrices as $tier) { + if ($tier['website_id'] == 0) { + $compare = join('-', array($tier['website_id'], $tier['cust_group'], $tier['price_qty'] * 1)); + $duplicates[$compare] = true; + } + } + } + // validate currency $baseCurrency = Mage::app()->getBaseCurrencyCode(); $rates = $this->_getWebsiteRates(); @@ -126,7 +139,7 @@ public function validate($object) } $compare = join('-', array($tier['website_id'], $tier['cust_group'], $tier['price_qty'])); - $globalCompare = join('-', array(0, $tier['cust_group'], $tier['price_qty'])); + $globalCompare = join('-', array(0, $tier['cust_group'], $tier['price_qty'] * 1)); $websiteCurrency = $rates[$tier['website_id']]['code']; if ($baseCurrency == $websiteCurrency && isset($duplicates[$globalCompare])) { @@ -135,7 +148,8 @@ public function validate($object) ); } } - return $this; + + return true; } /** @@ -146,8 +160,15 @@ public function validate($object) */ public function afterLoad($object) { - $data = $this->_getResource() - ->loadProductPrices($object, $this->getAttribute()); + $storeId = $object->getStoreId(); + $websiteId = null; + if ($this->getAttribute()->isScopeGlobal()) { + $websiteId = 0; + } else if ($storeId) { + $websiteId = Mage::app()->getStore($storeId)->getWebsiteId(); + } + + $data = $this->_getResource()->loadPriceData($object->getId(), $websiteId); foreach ($data as $k => $v) { $data[$k]['website_price'] = $v['price']; if ($v['all_groups']) { @@ -155,11 +176,7 @@ public function afterLoad($object) } } - if (!$object->getData('_edit_mode') - && $this->getAttribute()->getIsGlobal() == Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_WEBSITE - && ($storeId = $object->getStoreId())) - { - $websiteId = Mage::app()->getStore($storeId)->getWebsiteId(); + if (!$object->getData('_edit_mode') && $websiteId) { $rates = $this->_getWebsiteRates(); $full = $data; @@ -169,8 +186,7 @@ public function afterLoad($object) if ($v['website_id'] == $websiteId) { $data[$key] = $v; $data[$key]['website_price'] = $v['price']; - } - elseif ($v['website_id'] == 0 && !isset($data[$key])) { + } else if ($v['website_id'] == 0 && !isset($data[$key])) { $data[$key] = $v; $data[$key]['website_id'] = $websiteId; $data[$key]['price'] = $v['price'] * $rates[$websiteId]['rate']; @@ -179,28 +195,13 @@ public function afterLoad($object) } } -// foreach ($data as $i => $row) { -// if (!empty($row['all_groups'])) { -// $data[$i]['cust_group'] = Mage_Customer_Model_Group::CUST_GROUP_ALL; -// } -// if ($data[$i]['website_id'] == 0) { -// $rate = Mage::app()->getStore()->getBaseCurrency() -// ->getRate(Mage::app()->getBaseCurrencyCode()); -// if ($rate) { -// $data[$i]['website_price'] = $data[$i]['price']/$rate; -// } -// else { -// /** -// * Remove tier price if rate not available -// */ -// unset($data[$i]); -// } -// } -// else { -// $data[$i]['website_price'] = $data[$i]['price']; -// } -// } $object->setData($this->getAttribute()->getName(), $data); + $object->setOrigData($this->getAttribute()->getName(), $data); + + $valueChangedKey = $this->getAttribute()->getName() . '_changed'; + $object->setOrigData($valueChangedKey, 0); + $object->setData($valueChangedKey, 0); + return $this; } @@ -212,96 +213,100 @@ public function afterLoad($object) */ public function afterSave($object) { - $this->_getResource()->deleteProductPrices($object, $this->getAttribute()); - $tierPrices = $object->getData($this->getAttribute()->getName()); + $websiteId = Mage::app()->getStore($object->getStoreId())->getWebsiteId(); + $isGlobal = $this->getAttribute()->isScopeGlobal() || $websiteId == 0; - if (!is_array($tierPrices)) { + $tierPrices = $object->getData($this->getAttribute()->getName()); + if (empty($tierPrices)) { + if ($isGlobal) { + $this->_getResource()->deletePriceData($object->getId()); + } else { + $this->_getResource()->deletePriceData($object->getId(), $websiteId); + } return $this; } - $prices = array(); - foreach ($tierPrices as $tierPrice) { - if (empty($tierPrice['price_qty']) || !isset($tierPrice['price']) || !empty($tierPrice['delete'])) { - continue; + $old = array(); + $new = array(); + + // prepare original data for compare + $origTierPrices = $object->getOrigData($this->getAttribute()->getName()); + foreach ($origTierPrices as $data) { + if ($data['website_id'] > 0 || ($data['website_id'] == '0' && $isGlobal)) { + $key = join('-', array($data['website_id'], $data['cust_group'], $data['price_qty'] * 1)); + $old[$key] = $data; } + } - if (intval($tierPrice['website_id']) > 0 && - (!is_array($object->getWebsiteIds()) || !in_array($tierPrice['website_id'], $object->getWebsiteIds()))) { + // prepare data for save + foreach ($tierPrices as $data) { + if (empty($data['price_qty']) || !isset($data['price_qty']) || !empty($data['delete'])) { continue; } - - if ($object->getStoreId() && - Mage::app()->getStore($object->getStoreId())->getWebsiteId() != $tierPrice['website_id']) { + if ($this->getAttribute()->isScopeGlobal() && $data['website_id'] > 0) { continue; } + if (!$isGlobal && (int)$data['website_id'] == 0) { + continue; + } + + $key = join('-', array($data['website_id'], $data['cust_group'], $data['price_qty'] * 1)); + + $useForAllGroups = $data['cust_group'] == Mage_Customer_Model_Group::CUST_GROUP_ALL; + $customerGroupId = !$useForAllGroups ? $data['cust_group'] : 0; - $useForAllGroups = $tierPrice['cust_group'] == Mage_Customer_Model_Group::CUST_GROUP_ALL; - $customerGroupId = !$useForAllGroups ? $tierPrice['cust_group'] : 0; - $priceKey = join('-', array( - $tierPrice['website_id'], - intval($useForAllGroups), - $customerGroupId, - $tierPrice['price_qty'] - )); - - $prices[$priceKey] = array( - 'website_id' => $tierPrice['website_id'], - 'all_groups' => intval($useForAllGroups), + $new[$key] = array( + 'website_id' => $data['website_id'], + 'all_groups' => $useForAllGroups ? 1 : 0, 'customer_group_id' => $customerGroupId, - 'qty' => $tierPrice['price_qty'], - 'value' => $tierPrice['price'], + 'qty' => $data['price_qty'], + 'value' => $data['price'], ); } - if ($this->getAttribute()->getIsGlobal() == Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_WEBSITE) { - if ($storeId = $object->getStoreId()) { - $websites = array(Mage::app()->getStore($storeId)->getWebsite()); + $delete = array_diff_key($old, $new); + $insert = array_diff_key($new, $old); + $update = array_intersect_key($new, $old); + + $isChanged = false; + $productId = $object->getId(); + + if (!empty($delete)) { + foreach ($delete as $data) { + $this->_getResource()->deletePriceData($productId, null, $data['price_id']); + $isChanged = true; } - else { - $websites = Mage::app()->getWebsites(); + } + + if (!empty($insert)) { + foreach ($insert as $data) { + $price = new Varien_Object($data); + $price->setEntityId($productId); + $this->_getResource()->savePriceData($price); + + $isChanged = true; } + } - $baseCurrency = Mage::app()->getBaseCurrencyCode(); - $rates = $this->_getWebsiteRates(); - foreach ($websites as $website) { - /* @var $website Mage_Core_Model_Website */ - if (!is_array($object->getWebsiteIds()) || !in_array($website->getId(), $object->getWebsiteIds())) { - continue; - } - if ($rates[$website->getId()]['code'] != $baseCurrency) { - foreach ($prices as $data) { - $priceKey = join('-', array( - $website->getId(), - $data['all_groups'], - $data['customer_group_id'], - $data['qty'] - )); - if (!isset($prices[$priceKey])) { - $prices[$priceKey] = $data; - $prices[$priceKey]['website_id'] = $website->getId(); - $prices[$priceKey]['value'] = $data['value'] * $rates[$website->getId()]['rate']; - } - } + if (!empty($update)) { + foreach ($update as $k => $v) { + if ($old[$k]['price'] != $v['value']) { + $price = new Varien_Object(array( + 'value_id' => $old[$k]['price_id'], + 'value' => $v['value'] + )); + $this->_getResource()->savePriceData($price); + + $isChanged = true; } } } - foreach ($prices as $data) { - $this->_getResource()->insertProductPrice($object, $data); + if ($isChanged) { + $valueChangedKey = $this->getAttribute()->getName() . '_changed'; + $object->setData($valueChangedKey, 1); } return $this; } - - /** - * After delete object remove additional data - * - * @param Mage_Catalog_Model_Product $object - * @return Mage_Catalog_Model_Product_Attribute_Backend_Tierprice - */ - public function afterDelete($object) - { - $this->_getResource()->deleteProductPrices($object, $this->getAttribute()); - return $this; - } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Urlkey.php b/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Urlkey.php index 7b4fb10c46..6f724cea98 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Urlkey.php +++ b/app/code/core/Mage/Catalog/Model/Product/Attribute/Backend/Urlkey.php @@ -39,6 +39,9 @@ public function beforeSave($object) $attributeName = $this->getAttribute()->getName(); $urlKey = $object->getData($attributeName); + if ($urlKey === false) { + return $this; + } if ($urlKey == '') { $urlKey = $object->getName(); } @@ -51,10 +54,13 @@ public function beforeSave($object) public function afterSave($object) { /* @var $object Mage_Catalog_Model_Product */ - if (!$object->getExcludeUrlRewrite() && + /** + * Logic moved to Mage_Catalog_Model_Indexer_Url + */ + /*if (!$object->getExcludeUrlRewrite() && ($object->dataHasChangedFor('url_key') || $object->getIsChangedCategories() || $object->getIsChangedWebsites())) { Mage::getSingleton('catalog/url')->refreshProductRewrite($object->getId()); - } + }*/ return $this; } } diff --git a/app/code/core/Mage/Catalog/Model/Product/Attribute/Media/Api.php b/app/code/core/Mage/Catalog/Model/Product/Attribute/Media/Api.php index b217b527b1..b44658f1af 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Attribute/Media/Api.php +++ b/app/code/core/Mage/Catalog/Model/Product/Attribute/Media/Api.php @@ -62,9 +62,9 @@ public function __construct() * @param string|int $store * @return array */ - public function items($productId, $store = null) + public function items($productId, $store = null, $identifierType = null) { - $product = $this->_initProduct($productId, $store); + $product = $this->_initProduct($productId, $store, $identifierType); $gallery = $this->_getGalleryAttribute($product); @@ -91,9 +91,9 @@ public function items($productId, $store = null) * @param string|int $store * @return array */ - public function info($productId, $file, $store = null) + public function info($productId, $file, $store = null, $identifierType = null) { - $product = $this->_initProduct($productId, $store); + $product = $this->_initProduct($productId, $store, $identifierType); $gallery = $this->_getGalleryAttribute($product); @@ -112,9 +112,9 @@ public function info($productId, $file, $store = null) * @param string|int $store * @return string */ - public function create($productId, $data, $store = null) + public function create($productId, $data, $store = null, $identifierType = null) { - $product = $this->_initProduct($productId, $store); + $product = $this->_initProduct($productId, $store, $identifierType); $gallery = $this->_getGalleryAttribute($product); @@ -187,9 +187,9 @@ public function create($productId, $data, $store = null) * @param string|int $store * @return boolean */ - public function update($productId, $file, $data, $store = null) + public function update($productId, $file, $data, $store = null, $identifierType = null) { - $product = $this->_initProduct($productId, $store); + $product = $this->_initProduct($productId, $store, $identifierType); $gallery = $this->_getGalleryAttribute($product); @@ -232,9 +232,9 @@ public function update($productId, $file, $data, $store = null) * @param string $file * @return boolean */ - public function remove($productId, $file) + public function remove($productId, $file, $identifierType = null) { - $product = $this->_initProduct($productId); + $product = $this->_initProduct($productId, null, $identifierType); $gallery = $this->_getGalleryAttribute($product); @@ -354,18 +354,27 @@ protected function _imageToArray(&$image, $product) * @param string|int $store * @return Mage_Catalog_Model_Product */ - protected function _initProduct($productId, $store = null) + protected function _initProduct($productId, $store = null, $identifierType = null) { + $loadByIdOnFalse = false; + if ($identifierType === null) { + $identifierType = 'sku'; + $loadByIdOnFalse = true; + } + /* @var $product Mage_Catalog_Model_Product */ $product = Mage::getModel('catalog/product') ->setStoreId($this->_getStoreId($store)); - - $idBySku = $product->getIdBySku($productId); - if ($idBySku) { - $productId = $idBySku; + if ($identifierType == 'sku') { + $idBySku = $product->getIdBySku($productId); + if ($idBySku) { + $productId = $idBySku; + } + if ($idBySku || $loadByIdOnFalse) { + $product->load($productId); + } + } elseif ($identifierType == 'id') { + $product->load($productId); } - /* @var $product Mage_Catalog_Model_Product */ - - $product->load($productId); if (!$product->getId()) { $this->_fault('product_not_exists'); diff --git a/app/code/core/Mage/Catalog/Model/Product/Attribute/Media/Api/V2.php b/app/code/core/Mage/Catalog/Model/Product/Attribute/Media/Api/V2.php index 7f00e2b8af..b3a0a7115e 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Attribute/Media/Api/V2.php +++ b/app/code/core/Mage/Catalog/Model/Product/Attribute/Media/Api/V2.php @@ -65,9 +65,9 @@ protected function _prepareImageData($data) * @param string|int $store * @return string */ - public function create($productId, $data, $store = null) + public function create($productId, $data, $store = null, $identifierType = null) { - $product = $this->_initProduct($productId, $store); + $product = $this->_initProduct($productId, $store, $identifierType); $gallery = $this->_getGalleryAttribute($product); @@ -142,9 +142,9 @@ public function create($productId, $data, $store = null) * @param string|int $store * @return boolean */ - public function update($productId, $file, $data, $store = null) + public function update($productId, $file, $data, $store = null, $identifierType = null) { - $product = $this->_initProduct($productId, $store); + $product = $this->_initProduct($productId, $store, $identifierType); $gallery = $this->_getGalleryAttribute($product); diff --git a/app/code/core/Mage/Catalog/Model/Product/Attribute/Tierprice/Api.php b/app/code/core/Mage/Catalog/Model/Product/Attribute/Tierprice/Api.php index 5fd156bf21..0cee46bf5b 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Attribute/Tierprice/Api.php +++ b/app/code/core/Mage/Catalog/Model/Product/Attribute/Tierprice/Api.php @@ -40,9 +40,9 @@ public function __construct() $this->_storeIdSessionField = 'product_store_id'; } - public function info($productId) + public function info($productId, $identifierType = null) { - $product = $this->_initProduct($productId); + $product = $this->_initProduct($productId, $identifierType); $tierPrices = $product->getData(self::ATTRIBUTE_CODE); if (!is_array($tierPrices)) { @@ -64,9 +64,9 @@ public function info($productId) return $result; } - public function update($productId, $tierPrices) + public function update($productId, $tierPrices, $identifierType = null) { - $product = $this->_initProduct($productId); + $product = $this->_initProduct($productId, $identifierType); if (!is_array($tierPrices)) { $this->_fault('data_invalid', Mage::helper('catalog')->__('Invalid Tier Prices')); } @@ -136,18 +136,28 @@ public function update($productId, $tierPrices) * @param string|int $store * @return Mage_Catalog_Model_Product */ - protected function _initProduct($productId) + protected function _initProduct($productId, $identifierType = null) { + $loadByIdOnFalse = false; + if ($identifierType === null) { + $identifierType = 'sku'; + $loadByIdOnFalse = true; + } $product = Mage::getModel('catalog/product') ->setStoreId($this->_getStoreId()); - $idBySku = $product->getIdBySku($productId); - if ($idBySku) { - $productId = $idBySku; + if ($identifierType == 'sku') { + $idBySku = $product->getIdBySku($productId); + if ($idBySku) { + $productId = $idBySku; + } + if ($idBySku || $loadByIdOnFalse) { + $product->load($productId); + } + } elseif ($identifierType == 'id') { + $product->load($productId); } - $product->load($productId); - /* @var $product Mage_Catalog_Model_Product */ if (!$product->getId()) { diff --git a/app/code/core/Mage/Catalog/Model/Product/Attribute/Tierprice/Api/V2.php b/app/code/core/Mage/Catalog/Model/Product/Attribute/Tierprice/Api/V2.php index 31ad005979..e0352e4f33 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Attribute/Tierprice/Api/V2.php +++ b/app/code/core/Mage/Catalog/Model/Product/Attribute/Tierprice/Api/V2.php @@ -40,9 +40,9 @@ class Mage_Catalog_Model_Product_Attribute_Tierprice_Api_V2 extends Mage_Catalog * @param array $tierPrices * @return boolean */ - public function update($productId, $tierPrices) + public function update($productId, $tierPrices, $identifierType = null) { - $product = $this->_initProduct($productId); + $product = $this->_initProduct($productId, $identifierType); if (!is_array($tierPrices)) { $this->_fault('data_invalid', Mage::helper('catalog')->__('Invalid Tier Prices')); } diff --git a/app/code/core/Mage/Catalog/Model/Product/Image.php b/app/code/core/Mage/Catalog/Model/Product/Image.php index 0cb8af56ca..dd1def7443 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Image.php +++ b/app/code/core/Mage/Catalog/Model/Product/Image.php @@ -35,6 +35,7 @@ class Mage_Catalog_Model_Product_Image extends Mage_Core_Model_Abstract { protected $_width; protected $_height; + protected $_quality = 90; protected $_keepAspectRatio = true; protected $_keepFrame = true; @@ -47,6 +48,8 @@ class Mage_Catalog_Model_Product_Image extends Mage_Core_Model_Abstract protected $_processor; protected $_destinationSubdir; protected $_angle; + + protected $_watermarkFile; protected $_watermarkPosition; protected $_watermarkWidth; protected $_watermarkHeigth; @@ -80,6 +83,28 @@ public function getHeight() return $this->_height; } + /** + * Set image quality, values in percentage from 0 to 100 + * + * @param int $quality + * @return Mage_Catalog_Model_Product_Image + */ + public function setQuality($quality) + { + $this->_quality = $quality; + return $this; + } + + /** + * Get image quality + * + * @return int + */ + public function getQuality() + { + return $this->_quality; + } + /** * @return Mage_Catalog_Model_Product_Image */ @@ -230,7 +255,7 @@ private function _rgbToString($rgbArray) */ public function setBaseFile($file) { - if (($file) && (0 !== strpos($file, '/', 0))) { + if (($file) && (0 !== strpos($file, '/', 0))) { $file = '/' . $file; } $baseDir = Mage::getSingleton('catalog/product_media_config')->getBaseMediaPath(); @@ -280,17 +305,29 @@ public function setBaseFile($file) ); if((!empty($this->_width)) || (!empty($this->_height))) $path[] = "{$this->_width}x{$this->_height}"; - // add misc params as a hash - $path[] = md5( - implode('_', array( + + // add misk params as a hash + $miscParams = array( ($this->_keepAspectRatio ? '' : 'non') . 'proportional', ($this->_keepFrame ? '' : 'no') . 'frame', ($this->_keepTransparency ? '' : 'no') . 'transparency', ($this->_constrainOnly ? 'do' : 'not') . 'constrainonly', $this->_rgbToString($this->_backgroundColor), - 'angle' . $this->_angle - )) + 'angle' . $this->_angle, + 'quality' . $this->_quality ); + + // if has watermark add watermark params to hash + if ($this->getWatermarkFile()) { + $miscParams[] = $this->getWatermarkFile(); + $miscParams[] = $this->getWatermarkImageOpacity(); + $miscParams[] = $this->getWatermarkPosition(); + $miscParams[] = $this->getWatermarkWidth(); + $miscParams[] = $this->getWatermarkHeigth(); + } + + $path[] = md5(implode('_', $miscParams)); + // append prepared filename $this->_newFile = implode('/', $path) . $file; // the $file contains heading slash @@ -333,6 +370,7 @@ public function getImageProcessor() $this->_processor->keepTransparency($this->_keepTransparency); $this->_processor->constrainOnly($this->_constrainOnly); $this->_processor->backgroundColor($this->_backgroundColor); + $this->_processor->quality($this->_quality); return $this->_processor; } @@ -374,40 +412,45 @@ public function setAngle($angle) } /** + * Add watermark to image + * size param in format 100x200 + * + * @param string $fileName + * @param string $position + * @param string $size + * @param int $width + * @param int $heigth + * @param int $imageOpacity * @return Mage_Catalog_Model_Product_Image */ public function setWatermark($file, $position=null, $size=null, $width=null, $heigth=null, $imageOpacity=null) { - $filename = false; - - if( !$file ) { + if ($file) { + $this->setWatermarkFile($file); + } else { return $this; } - $baseDir = Mage::getSingleton('catalog/product_media_config')->getBaseMediaPath(); - - if( file_exists($baseDir . '/watermark/stores/' . Mage::app()->getStore()->getId() . $file) ) { - $filename = $baseDir . '/watermark/stores/' . Mage::app()->getStore()->getId() . $file; - } elseif ( file_exists($baseDir . '/watermark/websites/' . Mage::app()->getWebsite()->getId() . $file) ) { - $filename = $baseDir . '/watermark/websites/' . Mage::app()->getWebsite()->getId() . $file; - } elseif ( file_exists($baseDir . '/watermark/default/' . $file) ) { - $filename = $baseDir . '/watermark/default/' . $file; - } elseif ( file_exists($baseDir . '/watermark/' . $file) ) { - $filename = $baseDir . '/watermark/' . $file; - } else { - $baseDir = Mage::getDesign()->getSkinBaseDir(); - if( file_exists($baseDir . $file) ) { - $filename = $baseDir . $file; - } - } + if ($position) + $this->setWatermarkPosition($position); + if ($size) + $this->setWatermarkSize($size); + if ($width) + $this->setWatermarkWidth($width); + if ($heigth) + $this->setWatermarkHeigth($heigth); + if ($imageOpacity) + $this->setImageOpacity($imageOpacity); + + $filePath = $this->_getWatermarkFilePath(); - if( $filename ) { + if($filePath) { $this->getImageProcessor() - ->setWatermarkPosition( ($position) ? $position : $this->getWatermarkPosition() ) - ->setWatermarkImageOpacity( ($imageOpacity) ? $imageOpacity : $this->getWatermarkImageOpacity() ) - ->setWatermarkWidth( ($width) ? $width : $this->getWatermarkWidth() ) - ->setWatermarkHeigth( ($heigth) ? $heigth : $this->getWatermarkHeigth() ) - ->watermark($filename); + ->setWatermarkPosition( $this->getWatermarkPosition() ) + ->setWatermarkImageOpacity( $this->getWatermarkImageOpacity() ) + ->setWatermarkWidth( $this->getWatermarkWidth() ) + ->setWatermarkHeigth( $this->getWatermarkHeigth() ) + ->watermark($filePath); } return $this; @@ -458,8 +501,68 @@ public function isCached() { return file_exists($this->_newFile); } + + /** + * Set watermark file name + * + * @param string $file + * @return Mage_Catalog_Model_Product_Image + */ + public function setWatermarkFile($file) + { + $this->_watermarkFile = $file; + return $this; + } /** + * Get watermark file name + * + * @return string + */ + public function getWatermarkFile() + { + return $this->_watermarkFile; + } + + /** + * Get relative watermark file path + * or false if file not found + * + * @return string | bool + */ + protected function _getWatermarkFilePath() + { + $filePath = false; + + if (!$file = $this->getWatermarkFile()) + { + return $filePath; + } + + $baseDir = Mage::getSingleton('catalog/product_media_config')->getBaseMediaPath(); + + if( file_exists($baseDir . '/watermark/stores/' . Mage::app()->getStore()->getId() . $file) ) { + $filePath = $baseDir . '/watermark/stores/' . Mage::app()->getStore()->getId() . $file; + } elseif ( file_exists($baseDir . '/watermark/websites/' . Mage::app()->getWebsite()->getId() . $file) ) { + $filePath = $baseDir . '/watermark/websites/' . Mage::app()->getWebsite()->getId() . $file; + } elseif ( file_exists($baseDir . '/watermark/default/' . $file) ) { + $filePath = $baseDir . '/watermark/default/' . $file; + } elseif ( file_exists($baseDir . '/watermark/' . $file) ) { + $filePath = $baseDir . '/watermark/' . $file; + } else { + $baseDir = Mage::getDesign()->getSkinBaseDir(); + if( file_exists($baseDir . $file) ) { + $filePath = $baseDir . $file; + } + } + + return $filePath; + } + + /** + * Set watermark position + * + * @param string $position * @return Mage_Catalog_Model_Product_Image */ public function setWatermarkPosition($position) @@ -468,23 +571,42 @@ public function setWatermarkPosition($position) return $this; } + /** + * Get watermark position + * + * @return string + */ public function getWatermarkPosition() { return $this->_watermarkPosition; } - + + /** + * Set watermark image opacity + * + * @param int $imageOpacity + * @return Mage_Catalog_Model_Product_Image + */ public function setWatermarkImageOpacity($imageOpacity) { $this->_watermarkImageOpacity = $imageOpacity; return $this; } + /** + * Get watermark image opacity + * + * @return int + */ public function getWatermarkImageOpacity() { return $this->_watermarkImageOpacity; } /** + * Set watermark size + * + * @param array $size * @return Mage_Catalog_Model_Product_Image */ public function setWatermarkSize($size) @@ -497,6 +619,9 @@ public function setWatermarkSize($size) } /** + * Set watermark width + * + * @param int $width * @return Mage_Catalog_Model_Product_Image */ public function setWatermarkWidth($width) @@ -505,12 +630,20 @@ public function setWatermarkWidth($width) return $this; } + /** + * Get watermark width + * + * @return int + */ public function getWatermarkWidth() { return $this->_watermarkWidth; } /** + * Set watermark heigth + * + * @param int $heigth * @return Mage_Catalog_Model_Product_Image */ public function setWatermarkHeigth($heigth) @@ -519,6 +652,11 @@ public function setWatermarkHeigth($heigth) return $this; } + /** + * Get watermark heigth + * + * @return string + */ public function getWatermarkHeigth() { return $this->_watermarkHeigth; diff --git a/app/code/core/Mage/Catalog/Model/Product/Indexer/Eav.php b/app/code/core/Mage/Catalog/Model/Product/Indexer/Eav.php new file mode 100644 index 0000000000..aaa9e471fe --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Product/Indexer/Eav.php @@ -0,0 +1,259 @@ + array( + Mage_Index_Model_Event::TYPE_SAVE, + Mage_Index_Model_Event::TYPE_DELETE, + Mage_Index_Model_Event::TYPE_MASS_ACTION, + ), + Mage_Catalog_Model_Resource_Eav_Attribute::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE, + ), + Mage_Catalog_Model_Convert_Adapter_Product::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ) + ); + + /** + * Retrieve Indexer name + * + * @return string + */ + public function getName() + { + return Mage::helper('catalog')->__('Product Attributes'); + } + + /** + * Retrieve Indexer description + * + * @return string + */ + public function getDescription() + { + return Mage::helper('catalog')->__('Index product attributes for layered navigation building'); + } + + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('catalog/product_indexer_eav'); + } + + /** + * Register data required by process in event object + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerEvent(Mage_Index_Model_Event $event) + { + $entity = $event->getEntity(); + + if ($entity == Mage_Catalog_Model_Product::ENTITY) { + switch ($event->getType()) { + case Mage_Index_Model_Event::TYPE_DELETE: + $this->_registerCatalogProductDeleteEvent($event); + break; + + case Mage_Index_Model_Event::TYPE_SAVE: + $this->_registerCatalogProductSaveEvent($event); + break; + + case Mage_Index_Model_Event::TYPE_MASS_ACTION: + $this->_registerCatalogProductMassActionEvent($event); + break; + } + } else if ($entity == Mage_Catalog_Model_Resource_Eav_Attribute::ENTITY) { + switch ($event->getType()) { + case Mage_Index_Model_Event::TYPE_SAVE: + $this->_registerCatalogAttributeSaveEvent($event); + break; + } + } else if ($entity == Mage_Catalog_Model_Convert_Adapter_Product::ENTITY) { + $event->addNewData('catalog_product_eav_reindex_all', true); + } + } + + /** + * Check is attribute indexable in EAV + * + * @param Mage_Catalog_Model_Resource_Eav_Attribute|string $attribute + * @return bool + */ + protected function _attributeIsIndexable($attribute) + { + if (!$attribute instanceof Mage_Catalog_Model_Resource_Eav_Attribute) { + $attribute = Mage::getSingleton('eav/config') + ->getAttribute('catalog_product', $attribute); + } + + return (($attribute->getBackendType() == 'int' && $attribute->getFrontendInput() == 'select') + || ($attribute->getBackendType() == 'varchar' && $attribute->getFrontendInput() == 'multiselect')) + && ($attribute->getIsFilterable() || $attribute->getIsFilterableInSearch()); + } + + /** + * Register data required by process in event object + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Product_Indexer_Eav + */ + protected function _registerCatalogProductSaveEvent(Mage_Index_Model_Event $event) + { + /* @var $product Mage_Catalog_Model_Product */ + $product = $event->getDataObject(); + $attributes = $product->getAttributes(); + $reindexEav = false; + foreach ($attributes as $attribute) { + $attributeCode = $attribute->getAttributeCode(); + if ($this->_attributeIsIndexable($attribute) && $product->dataHasChangedFor($attributeCode)) { + $reindexEav = true; + break; + } + } + + if ($reindexEav) { + $event->addNewData('reindex_eav', $reindexEav); + } + + return $this; + } + + /** + * Register data required by process in event object + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Product_Indexer_Eav + */ + protected function _registerCatalogProductDeleteEvent(Mage_Index_Model_Event $event) + { + /* @var $product Mage_Catalog_Model_Product */ + $product = $event->getDataObject(); + + $parentIds = $this->_getResource()->getRelationsByChild($product->getId()); + if ($parentIds) { + $event->addNewData('reindex_eav_parent_ids', $parentIds); + } + + return $this; + } + + /** + * Register data required by process in event object + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Product_Indexer_Eav + */ + protected function _registerCatalogProductMassActionEvent(Mage_Index_Model_Event $event) + { + $reindexEav = false; + + /* @var $actionObject Varien_Object */ + $actionObject = $event->getDataObject(); + // check if attributes changed + $attrData = $actionObject->getAttributesData(); + if (is_array($attrData)) { + foreach (array_keys($attrData) as $attributeCode) { + if ($this->_attributeIsIndexable($attributeCode)) { + $reindexEav = true; + break; + } + } + } + + // check changed websites + if ($actionObject->getWebsiteIds()) { + $reindexEav = true; + } + + // register affected products + if ($reindexEav) { + $event->addNewData('reindex_eav_product_ids', $actionObject->getProductIds()); + } + + return $this; + } + + /** + * Register data required by process attribute save in event object + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Product_Indexer_Eav + */ + protected function _registerCatalogAttributeSaveEvent(Mage_Index_Model_Event $event) + { + /* @var $attribute Mage_Catalog_Model_Resource_Eav_Attribute */ + $attribute = $event->getDataObject(); + + $validateType = (($attribute->getBackendType() == 'int' && $attribute->getFrontendInput() == 'select') + || ($attribute->getBackendType() == 'varchar' && $attribute->getFrontendInput() == 'multiselect')); + if ($validateType) { + $before = $attribute->getOrigData('is_filterable') + || $attribute->getOrigData('is_filterable_in_search'); + $after = $attribute->getData('is_filterable') + || $attribute->getData('is_filterable_in_search'); + + if (!$before && $after || $before && !$after) { + $event->addNewData('reindex_attribute', 1); + $event->addNewData('is_indexable', $after); + } + } + + return $this; + } + + /** + * Process event + * + * @param Mage_Index_Model_Event $event + */ + protected function _processEvent(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + if (!empty($data['catalog_product_eav_reindex_all'])) { + $this->reindexAll(); + } + if (empty($data['catalog_product_eav_skip_call_event_handler'])) { + $this->callEventHandler($event); + } + } +} diff --git a/app/code/core/Mage/Catalog/Model/Product/Indexer/Flat.php b/app/code/core/Mage/Catalog/Model/Product/Indexer/Flat.php new file mode 100644 index 0000000000..8e94b4b8fb --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Product/Indexer/Flat.php @@ -0,0 +1,287 @@ + array( + Mage_Index_Model_Event::TYPE_SAVE, + Mage_Index_Model_Event::TYPE_MASS_ACTION, + ), + Mage_Catalog_Model_Resource_Eav_Attribute::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE, + Mage_Index_Model_Event::TYPE_DELETE, + ), + Mage_Core_Model_Store::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE, + Mage_Index_Model_Event::TYPE_DELETE + ), + Mage_Core_Model_Store_Group::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Catalog_Model_Convert_Adapter_Product::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ) + ); + + /** + * Retrieve Indexer name + * + * @return string + */ + public function getName() + { + return Mage::helper('catalog')->__('Product Flat Data'); + } + + /** + * Retrieve Indexer description + * + * @return string + */ + public function getDescription() + { + return Mage::helper('catalog')->__('Reorganize EAV product structure to flat structure'); + } + + /** + * Retrieve Catalog Product Flat Indexer model + * + * @return Mage_Catalog_Model_Product_Flat_Indexer + */ + protected function _getIndexer() + { + return Mage::getSingleton('catalog/product_flat_indexer'); + } + + /** + * Check if event can be matched by process + * Overwrote for check is flat catalog product is enabled and specific save + * attribute, store, store_group + * + * @param Mage_Index_Model_Event $event + * @return bool + */ + public function matchEvent(Mage_Index_Model_Event $event) + { + if (!Mage::helper('catalog/product_flat')->isBuilt()) { + return false; + } + + $entity = $event->getEntity(); + if ($entity == Mage_Catalog_Model_Resource_Eav_Attribute::ENTITY) { + /* @var $attribute Mage_Catalog_Model_Resource_Eav_Attribute */ + $attribute = $event->getDataObject(); + $addFilterable = Mage::helper('catalog/product_flat')->isAddFilterableAttributes(); + + $enableBefore = ($attribute->getOrigData('backend_type') == 'static') + || ($addFilterable && $attribute->getOrigData('is_filterable') > 0) + || ($attribute->getOrigData('used_in_product_listing') == 1) + || ($attribute->getOrigData('used_for_sort_by') == 1); + + $enableAfter = ($attribute->getData('backend_type') == 'static') + || ($addFilterable && $attribute->getData('is_filterable') > 0) + || ($attribute->getData('used_in_product_listing') == 1) + || ($attribute->getData('used_for_sort_by') == 1); + + if ($event->getType() == Mage_Index_Model_Event::TYPE_DELETE) { + return $enableBefore; + } else if ($event->getType() == Mage_Index_Model_Event::TYPE_SAVE) { + if ($enableAfter || $enableBefore) { + return true; + } + return false; + } + } else if ($entity == Mage_Core_Model_Store::ENTITY) { + if ($event->getType() == Mage_Index_Model_Event::TYPE_DELETE) { + return true; + } + + /* @var $store Mage_Core_Model_Store */ + $store = $event->getDataObject(); + if ($store->isObjectNew()) { + return true; + } + return false; + } else if ($entity == Mage_Core_Model_Store_Group::ENTITY) { + /* @var $storeGroup Mage_Core_Model_Store_Group */ + $storeGroup = $event->getDataObject(); + if ($storeGroup->dataHasChangedFor('website_id')) { + return true; + } + return false; + } + + return parent::matchEvent($event); + } + + /** + * Register data required by process in event object + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerEvent(Mage_Index_Model_Event $event) + { + switch ($event->getEntity()) { + case Mage_Catalog_Model_Product::ENTITY: + $this->_registerCatalogProductEvent($event); + break; + case Mage_Catalog_Model_Convert_Adapter_Product::ENTITY: + $event->addNewData('catalog_product_flat_reindex_all', true); + break; + case Mage_Core_Model_Store::ENTITY: + if ($event->getType() == Mage_Index_Model_Event::TYPE_DELETE) { + $this->_registerCoreStoreEvent($event); + break; + } + case Mage_Catalog_Model_Resource_Eav_Attribute::ENTITY: + case Mage_Core_Model_Store_Group::ENTITY: + $event->addNewData('catalog_product_flat_skip_call_event_handler', true); + $process = $event->getProcess(); + $process->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX); + break; + } + } + + /** + * Register data required by catalog product process in event object + * + * @param Mage_Index_Model_Event $event + * @return Mage_CatalogSearch_Model_Indexer_Search + */ + protected function _registerCatalogProductEvent(Mage_Index_Model_Event $event) + { + switch ($event->getType()) { + case Mage_Index_Model_Event::TYPE_SAVE: + /* @var $product Mage_Catalog_Model_Product */ + $product = $event->getDataObject(); + $event->addNewData('catalog_product_flat_product_id', $product->getId()); + break; + + case Mage_Index_Model_Event::TYPE_MASS_ACTION: + /* @var $actionObject Varien_Object */ + $actionObject = $event->getDataObject(); + + $reindexData = array(); + $reindexFlat = false; + + // check if status changed + $attrData = $actionObject->getAttributesData(); + if (isset($attrData['status'])) { + $reindexFlat = true; + $reindexData['catalog_product_flat_status'] = $attrData['status']; + } + + // check changed websites + if ($actionObject->getWebsiteIds()) { + $reindexFlat = true; + $reindexData['catalog_product_flat_website_ids'] = $actionObject->getWebsiteIds(); + $reindexData['catalog_product_flat_action_type'] = $actionObject->getActionType(); + } + + // register affected products + if ($reindexFlat) { + $reindexData['catalog_product_flat_product_ids'] = $actionObject->getProductIds(); + foreach ($reindexData as $k => $v) { + $event->addNewData($k, $v); + } + } + break; + } + + return $this; + } + + /** + * Register core store delete process + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Product_Indexer_Flat + */ + protected function _registerCoreStoreEvent(Mage_Index_Model_Event $event) + { + if ($event->getType() == Mage_Index_Model_Event::TYPE_DELETE) { + /* @var $store Mage_Core_Model_Store */ + $store = $event->getDataObject(); + $event->addNewData('catalog_product_flat_delete_store_id', $store->getId()); + } + return $this; + } + + /** + * Process event + * + * @param Mage_Index_Model_Event $event + */ + protected function _processEvent(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + if (!empty($data['catalog_product_flat_reindex_all'])) { + $this->reindexAll(); + } else if (!empty($data['catalog_product_flat_product_id'])) { + // catalog_product save + $productId = $data['catalog_product_flat_product_id']; + $this->_getIndexer()->saveProduct($productId); + } else if (!empty($data['catalog_product_flat_product_ids'])) { + // catalog_product mass_action + $productIds = $data['catalog_product_flat_product_ids']; + + if (!empty($data['catalog_product_flat_website_ids'])) { + $websiteIds = $data['catalog_product_flat_website_ids']; + foreach ($websiteIds as $websiteId) { + $website = Mage::app()->getWebsite($websiteId); + foreach ($website->getStores() as $store) { + if ($data['catalog_product_flat_action_type'] == 'remove') { + $this->_getIndexer()->removeProduct($productIds, $store->getId()); + } else { + $this->_getIndexer()->updateProduct($productIds, $store->getId()); + } + } + } + } + + if (isset($data['catalog_product_flat_status'])) { + $status = $data['catalog_product_flat_status']; + $this->_getIndexer()->updateProductStatus($productIds, $status); + } + } else if (!empty($data['catalog_product_flat_delete_store_id'])) { + $this->_getIndexer()->deleteStore($data['catalog_product_flat_delete_store_id']); + } + } + + /** + * Rebuild all index data + * + */ + public function reindexAll() + { + $this->_getIndexer()->rebuild(); + } +} diff --git a/app/code/core/Mage/Catalog/Model/Product/Indexer/Price.php b/app/code/core/Mage/Catalog/Model/Product/Indexer/Price.php new file mode 100644 index 0000000000..41af77ea6b --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Product/Indexer/Price.php @@ -0,0 +1,238 @@ + array( + Mage_Index_Model_Event::TYPE_SAVE, + Mage_Index_Model_Event::TYPE_DELETE, + Mage_Index_Model_Event::TYPE_MASS_ACTION, + ), + Mage_Core_Model_Config_Data::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Catalog_Model_Convert_Adapter_Product::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ) + ); + + protected $_relatedConfigSettings = array( + Mage_Catalog_Helper_Data::XML_PATH_PRICE_SCOPE + ); + + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('catalog/product_indexer_price'); + } + + /** + * Retrieve Indexer name + * + * @return string + */ + public function getName() + { + return Mage::helper('catalog')->__('Product Prices'); + } + + /** + * Retrieve Indexer description + * + * @return string + */ + public function getDescription() + { + return Mage::helper('catalog')->__('Index product prices'); + } + + /** + * Retrieve attribute list has an effect on product price + * + * @return array + */ + protected function _getDependentAttributes() + { + return array( + 'price', + 'special_price', + 'special_from_date', + 'special_to_date', + 'tax_class_id', + 'status', + ); + } + + /** + * Check if event can be matched by process. + * Rewrited for checking configuration settings save (like price scope). + * + * @param Mage_Index_Model_Event $event + * @return bool + */ + public function matchEvent(Mage_Index_Model_Event $event) + { + if ($event->getEntity() == Mage_Core_Model_Config_Data::ENTITY) { + $data = $event->getDataObject(); + if (in_array($data->getPath(), $this->_relatedConfigSettings)) { + return $data->isValueChanged(); + } else { + return false; + } + } + return parent::matchEvent($event); + } + + /** + * Register data required by catalog product delete process + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerCatalogProductDeleteEvent(Mage_Index_Model_Event $event) + { + /* @var $product Mage_Catalog_Model_Product */ + $product = $event->getDataObject(); + + $parentIds = $this->_getResource()->getProductParentsByChild($product->getId()); + if ($parentIds) { + $event->addNewData('reindex_price_parent_ids', $parentIds); + } + } + + /** + * Register data required by catalog product save process + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerCatalogProductSaveEvent(Mage_Index_Model_Event $event) + { + /* @var $product Mage_Catalog_Model_Product */ + $product = $event->getDataObject(); + $attributes = $this->_getDependentAttributes(); + $reindexPrice = $product->getIsRelationsChanged() || $product->getIsCustomOptionChanged() + || $product->dataHasChangedFor('tier_price_changed'); + + foreach ($attributes as $attributeCode) { + $reindexPrice = $reindexPrice || $product->dataHasChangedFor($attributeCode); + } + + if ($reindexPrice) { + $event->addNewData('product_type_id', $product->getTypeId()); + $event->addNewData('reindex_price', 1); + } + } + + protected function _registerCatalogProductMassActionEvent(Mage_Index_Model_Event $event) + { + /* @var $actionObject Varien_Object */ + $actionObject = $event->getDataObject(); + $attributes = $this->_getDependentAttributes(); + $reindexPrice = false; + + // check if attributes changed + $attrData = $actionObject->getAttributesData(); + if (is_array($attrData)) { + foreach ($attributes as $attributeCode) { + if (array_key_exists($attributeCode, $attrData)) { + $reindexPrice = true; + break; + } + } + } + + // check changed websites + if ($actionObject->getWebsiteIds()) { + $reindexPrice = true; + } + + // register affected products + if ($reindexPrice) { + $event->addNewData('reindex_price_product_ids', $actionObject->getProductIds()); + } + } + + /** + * Register data required by process in event object + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerEvent(Mage_Index_Model_Event $event) + { + $entity = $event->getEntity(); + + if ($entity == Mage_Core_Model_Config_Data::ENTITY) { + $process = $event->getProcess(); + $process->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX); + } else if ($entity == Mage_Catalog_Model_Convert_Adapter_Product::ENTITY) { + $event->addNewData('catalog_product_price_reindex_all', true); + } else if ($entity == Mage_Catalog_Model_Product::ENTITY) { + switch ($event->getType()) { + case Mage_Index_Model_Event::TYPE_DELETE: + $this->_registerCatalogProductDeleteEvent($event); + break; + + case Mage_Index_Model_Event::TYPE_SAVE: + $this->_registerCatalogProductSaveEvent($event); + break; + + case Mage_Index_Model_Event::TYPE_MASS_ACTION: + $this->_registerCatalogProductMassActionEvent($event); + break; + } + + // call product type indexers registerEvent + $indexers = $this->_getResource()->getTypeIndexers(); + foreach ($indexers as $indexer) { + $indexer->registerEvent($event); + } + } + } + + /** + * Process event + * + * @param Mage_Index_Model_Event $event + */ + protected function _processEvent(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + if (!empty($data['catalog_product_price_reindex_all'])) { + $this->reindexAll(); + } + if (empty($data['catalog_product_price_skip_call_event_handler'])) { + $this->callEventHandler($event); + } + } +} diff --git a/app/code/core/Mage/Catalog/Model/Product/Indexer/Status.php b/app/code/core/Mage/Catalog/Model/Product/Indexer/Status.php new file mode 100644 index 0000000000..f8d3181291 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Product/Indexer/Status.php @@ -0,0 +1,25 @@ +getGroupedLinkData(); if (!is_null($data)) { - $this->_getResource()->saveProductLinks($product, $data, self::LINK_TYPE_GROUPED); + $this->_getResource()->saveGroupedLinks($product, $data, self::LINK_TYPE_GROUPED); } return $this; } diff --git a/app/code/core/Mage/Catalog/Model/Product/Link/Api.php b/app/code/core/Mage/Catalog/Model/Product/Link/Api.php index 03a6ba61e7..928e632da8 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Link/Api.php +++ b/app/code/core/Mage/Catalog/Model/Product/Link/Api.php @@ -52,11 +52,11 @@ public function __construct() * @param int|sku $productId * @return array */ - public function items($type, $productId) + public function items($type, $productId, $identifierType = null) { $typeId = $this->_getTypeId($type); - $product = $this->_initProduct($productId); + $product = $this->_initProduct($productId, $identifierType); $link = $product->getLinkInstance() ->setLinkTypeId($typeId); @@ -92,11 +92,11 @@ public function items($type, $productId) * @param array $data * @return boolean */ - public function assign($type, $productId, $linkedProductId, $data = array()) + public function assign($type, $productId, $linkedProductId, $data = array(), $identifierType = null) { $typeId = $this->_getTypeId($type); - $product = $this->_initProduct($productId); + $product = $this->_initProduct($productId, $identifierType); $link = $product->getLinkInstance() ->setLinkTypeId($typeId); @@ -135,11 +135,11 @@ public function assign($type, $productId, $linkedProductId, $data = array()) * @param array $data * @return boolean */ - public function update($type, $productId, $linkedProductId, $data = array()) + public function update($type, $productId, $linkedProductId, $data = array(), $identifierType = null) { $typeId = $this->_getTypeId($type); - $product = $this->_initProduct($productId); + $product = $this->_initProduct($productId, $identifierType); $link = $product->getLinkInstance() ->setLinkTypeId($typeId); @@ -176,11 +176,11 @@ public function update($type, $productId, $linkedProductId, $data = array()) * @param int|string $linkedProductId * @return boolean */ - public function remove($type, $productId, $linkedProductId) + public function remove($type, $productId, $linkedProductId, $identifierType = null) { $typeId = $this->_getTypeId($type); - $product = $this->_initProduct($productId); + $product = $this->_initProduct($productId, $identifierType); $link = $product->getLinkInstance() ->setLinkTypeId($typeId); @@ -263,20 +263,27 @@ protected function _getTypeId($type) * @param int $productId * @return Mage_Catalog_Model_Product */ - protected function _initProduct($productId) + protected function _initProduct($productId, $identifierType = null) { - - + $loadByIdOnFalse = false; + if ($identifierType === null) { + $identifierType = 'sku'; + $loadByIdOnFalse = true; + } $product = Mage::getModel('catalog/product') ->setStoreId($this->_getStoreId()); - - $idBySku = $product->getIdBySku($productId); - if ($idBySku) { - $productId = $idBySku; + if ($identifierType == 'sku') { + $idBySku = $product->getIdBySku($productId); + if ($idBySku) { + $productId = $idBySku; + } + if ($idBySku || $loadByIdOnFalse) { + $product->load($productId); + } + } elseif ($identifierType == 'id') { + $product->load($productId); } - $product->load($productId); - if (!$product->getId()) { $this->_fault('product_not_exists'); } diff --git a/app/code/core/Mage/Catalog/Model/Product/Link/Api/V2.php b/app/code/core/Mage/Catalog/Model/Product/Link/Api/V2.php index fee59b80ed..eb8029fe84 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Link/Api/V2.php +++ b/app/code/core/Mage/Catalog/Model/Product/Link/Api/V2.php @@ -42,11 +42,11 @@ class Mage_Catalog_Model_Product_Link_Api_V2 extends Mage_Catalog_Model_Product_ * @param array $data * @return boolean */ - public function assign($type, $productId, $linkedProductId, $data = array()) + public function assign($type, $productId, $linkedProductId, $data = array(), $identifierType = null) { $typeId = $this->_getTypeId($type); - $product = $this->_initProduct($productId); + $product = $this->_initProduct($productId, $identifierType); $link = $product->getLinkInstance() ->setLinkTypeId($typeId); @@ -84,11 +84,11 @@ public function assign($type, $productId, $linkedProductId, $data = array()) * @param array $data * @return boolean */ - public function update($type, $productId, $linkedProductId, $data = array()) + public function update($type, $productId, $linkedProductId, $data = array(), $identifierType = null) { $typeId = $this->_getTypeId($type); - $product = $this->_initProduct($productId); + $product = $this->_initProduct($productId, $identifierType); $link = $product->getLinkInstance() ->setLinkTypeId($typeId); diff --git a/app/code/core/Mage/Catalog/Model/Product/Option/Type/Select.php b/app/code/core/Mage/Catalog/Model/Product/Option/Type/Select.php index 76d3ed94ec..b3db5e73d9 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Option/Type/Select.php +++ b/app/code/core/Mage/Catalog/Model/Product/Option/Type/Select.php @@ -116,11 +116,34 @@ public function getEditableOptionValue($optionValue) $result = ''; if (!$this->_isSingleSelection()) { foreach (explode(',', $optionValue) as $_value) { - $result .= $option->getValueById($_value)->getTitle() . ', '; + if ($_result = $option->getValueById($_value)) { + $result .= $_result->getTitle() . ', '; + } else { + if ($this->getListener()) { + $this->getListener() + ->setHasError(true) + ->setMessage( + Mage::helper('catalog')->__('Some of the products below don\'t have all the required options. Please remove them and add again with all the required options.') + ); + $result = ''; + break; + } + } } $result = Mage::helper('core/string')->substr($result, 0, -2); } elseif ($this->_isSingleSelection()) { - $result = $option->getValueById($optionValue)->getTitle(); + if ($_result = $option->getValueById($optionValue)) { + $result = $_result->getTitle(); + } else { + if ($this->getListener()) { + $this->getListener() + ->setHasError(true) + ->setMessage( + Mage::helper('catalog')->__('Some of the products below don\'t have all the required options. Please remove them and add again with all the required options.') + ); + } + $result = ''; + } } else { $result = $optionValue; } @@ -181,18 +204,39 @@ public function getOptionPrice($optionValue, $basePrice) if (!$this->_isSingleSelection()) { foreach(explode(',', $optionValue) as $value) { - $result += $this->_getChargableOptionPrice( - $option->getValueById($value)->getPrice(), - $option->getValueById($value)->getPriceType() == 'percent', + if ($_result = $option->getValueById($optionValue)) { + $result += $this->_getChargableOptionPrice( + $_result->getPrice(), + $_result->getPriceType() == 'percent', + $basePrice + ); + } else { + if ($this->getListener()) { + $this->getListener() + ->setHasError(true) + ->setMessage( + Mage::helper('catalog')->__('Some of the products below don\'t have all the required options. Please remove them and add again with all the required options.') + ); + break; + } + } + } + } elseif ($this->_isSingleSelection()) { + if ($_result = $option->getValueById($optionValue)) { + $result = $this->_getChargableOptionPrice( + $_result->getPrice(), + $_result->getPriceType() == 'percent', $basePrice ); + } else { + if ($this->getListener()) { + $this->getListener() + ->setHasError(true) + ->setMessage( + Mage::helper('catalog')->__('Some of the products below don\'t have all the required options. Please remove them and add again with all the required options.') + ); + } } - } elseif ($this->_isSingleSelection()) { - $result = $this->_getChargableOptionPrice( - $option->getValueById($optionValue)->getPrice(), - $option->getValueById($optionValue)->getPriceType() == 'percent', - $basePrice - ); } return $result; @@ -212,13 +256,33 @@ public function getOptionSku($optionValue, $skuDelimiter) if (!$this->_isSingleSelection()) { $skus = array(); foreach(explode(',', $optionValue) as $value) { - if ($optionSku = $option->getValueById($value)->getSku()) { - $skus[] = $optionSku; + if ($optionSku = $option->getValueById($value)) { + $skus[] = $optionSku->getSku(); + } else { + if ($this->getListener()) { + $this->getListener() + ->setHasError(true) + ->setMessage( + Mage::helper('catalog')->__('Some of the products below don\'t have all the required options. Please remove them and add again with all the required options.') + ); + break; + } } } $result = implode($skuDelimiter, $skus); } elseif ($this->_isSingleSelection()) { - $result = $option->getValueById($optionValue)->getSku(); + if ($result = $option->getValueById($optionValue)) { + return $result->getSku(); + } else { + if ($this->getListener()) { + $this->getListener() + ->setHasError(true) + ->setMessage( + Mage::helper('catalog')->__('Some of the products below don\'t have all the required options. Please remove them and add again with all the required options.') + ); + } + return ''; + } } else { $result = parent::getOptionSku($optionValue, $skuDelimiter); } diff --git a/app/code/core/Mage/Catalog/Model/Product/Status.php b/app/code/core/Mage/Catalog/Model/Product/Status.php index 63156fdb4d..c4ba27c582 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Status.php +++ b/app/code/core/Mage/Catalog/Model/Product/Status.php @@ -190,39 +190,28 @@ static public function getOptionText($optionId) */ public function updateProductStatus($productId, $storeId, $value) { - $stores = array(); - if ($storeId != 0) { - $attribute = $this->getProductAttribute('status'); - if ($attribute->getIsGlobal() == Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE) { - $stores[] = $storeId; - } - elseif ($attribute->getIsGlobal() == Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_WEBSITE) { - $website = Mage::app()->getStore($storeId)->getWebsite(); - foreach ($website->getStores() as $store) { - $stores[] = $store->getId(); - } - } - else { - $stores[] = 0; - } - } - else { - $stores[] = $storeId; + Mage::getSingleton('catalog/product_action') + ->updateAttributes(array($productId), array('status' => $value), $storeId); + + // add back compatibility event + $status = $this->_getResource()->getProductAttribute('status'); + if ($status->isScopeWebsite()) { + $website = Mage::app()->getStore($storeId)->getWebsite(); + $stores = $website->getStoreIds(); + } else if ($status->isScopeStore()) { + $stores = array($storeId); + } else { + $stores = array_keys(Mage::app()->getStores()); } foreach ($stores as $storeId) { - $this->_getResource()->updateProductStatus($productId, $storeId, $value); - Mage::getResourceModel('catalog/category')->refreshProductIndex( - array(), - array($productId), - $storeId ? array($storeId) : array() - ); Mage::dispatchEvent('catalog_product_status_update', array( 'product_id' => $productId, 'store_id' => $storeId, 'status' => $value )); } + return $this; } @@ -334,7 +323,7 @@ public function addValueSortToCollection($collection, $dir = 'asc') . " AND `{$valueTable2}`.`store_id`='{$collection->getStoreId()}'", array() ); - $valueExpr = new Zend_Db_Expr("IFNULL(`{$valueTable2}`.`value`, `{$valueTable1}`.`value`)"); + $valueExpr = new Zend_Db_Expr("IF(`{$valueTable2}`.`value_id`>0, `{$valueTable2}`.`value`, `{$valueTable1}`.`value`)"); } $collection->getSelect()->order($valueExpr . ' ' . $dir); diff --git a/app/code/core/Mage/Catalog/Model/Product/Type.php b/app/code/core/Mage/Catalog/Model/Product/Type.php index dfcd0cdf34..3209aa44e8 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Type.php +++ b/app/code/core/Mage/Catalog/Model/Product/Type.php @@ -49,6 +49,7 @@ class Mage_Catalog_Model_Product_Type static protected $_types; static protected $_compositeTypes; static protected $_priceModels; + static protected $_typesPriority; /** * Product type instance factory @@ -186,4 +187,39 @@ static public function getCompositeTypes() } return self::$_compositeTypes; } + + /** + * Return product types by type indexing priority + * + * @return array + */ + public static function getTypesByPriority() + { + if (is_null(self::$_typesPriority)) { + self::$_typesPriority = array(); + $a = array(); + $b = array(); + + $types = self::getTypes(); + foreach ($types as $typeId => $typeInfo) { + $priority = isset($typeInfo['index_priority']) ? abs(intval($typeInfo['index_priority'])) : 0; + if (!empty($typeInfo['composite'])) { + $b[$typeId] = $priority; + } else { + $a[$typeId] = $priority; + } + } + + asort($a, SORT_NUMERIC); + asort($b, SORT_NUMERIC); + + foreach (array_keys($a) as $typeId) { + self::$_typesPriority[$typeId] = $types[$typeId]; + } + foreach (array_keys($b) as $typeId) { + self::$_typesPriority[$typeId] = $types[$typeId]; + } + } + return self::$_typesPriority; + } } diff --git a/app/code/core/Mage/Catalog/Model/Product/Type/Abstract.php b/app/code/core/Mage/Catalog/Model/Product/Type/Abstract.php index 49ebbff289..cb3ba2e7d7 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Type/Abstract.php +++ b/app/code/core/Mage/Catalog/Model/Product/Type/Abstract.php @@ -486,11 +486,20 @@ public function getSku($product = null) $quoteItemOption = $this->getProduct($product)->getCustomOption('option_'.$optionId); $group = $option->groupFactory($option->getType()) - ->setOption($option); + ->setOption($option)->setListener(new Varien_Object()); if ($optionSku = $group->getOptionSku($quoteItemOption->getValue(), $skuDelimiter)) { $sku .= $skuDelimiter . $optionSku; } + + if ($group->getListener()->getHasError()) { + $this->getProduct($product) + ->setHasError(true) + ->setMessage( + $group->getListener()->getMessage() + ); + } + } } } diff --git a/app/code/core/Mage/Catalog/Model/Product/Type/Configurable.php b/app/code/core/Mage/Catalog/Model/Product/Type/Configurable.php index 5a92f96c67..b1840fabd5 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Type/Configurable.php +++ b/app/code/core/Mage/Catalog/Model/Product/Type/Configurable.php @@ -159,7 +159,8 @@ public function canUseAttribute(Mage_Eav_Model_Entity_Attribute $attribute) $allow = $attribute->getIsGlobal() == Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL && $attribute->getIsVisible() && $attribute->getIsConfigurable() - && $attribute->usesSource(); + && $attribute->usesSource() + && $attribute->getIsUserDefined(); return $allow; } @@ -257,15 +258,16 @@ public function getConfigurableAttributesAsArray($product = null) { $res = array(); foreach ($this->getConfigurableAttributes($product) as $attribute) { - $label = $attribute->getLabel() ? $attribute->getLabel() : $attribute->getProductAttribute()->getFrontend()->getLabel(); $res[] = array( - 'id' => $attribute->getId(), - 'label' => $label, - 'position' => $attribute->getPosition(), - 'values' => $attribute->getPrices() ? $attribute->getPrices() : array(), - 'attribute_id' => $attribute->getProductAttribute()->getId(), - 'attribute_code'=> $attribute->getProductAttribute()->getAttributeCode(), - 'frontend_label'=> $attribute->getProductAttribute()->getFrontend()->getLabel(), + 'id' => $attribute->getId(), + 'label' => $attribute->getLabel(), + 'use_default' => $attribute->getUseDefault(), + 'position' => $attribute->getPosition(), + 'values' => $attribute->getPrices() ? $attribute->getPrices() : array(), + 'attribute_id' => $attribute->getProductAttribute()->getId(), + 'attribute_code' => $attribute->getProductAttribute()->getAttributeCode(), + 'frontend_label' => $attribute->getProductAttribute()->getFrontend()->getLabel(), + 'store_label' => $attribute->getProductAttribute()->getStoreLabel(), ); } return $res; @@ -354,10 +356,12 @@ public function getUsedProductCollection($product = null) { $collection = Mage::getResourceModel('catalog/product_type_configurable_product_collection') ->setFlag('require_stock_items', true) + ->setFlag('product_children', true) ->setProductFilter($this->getProduct($product)); if (!is_null($this->getStoreFilter($product))) { $collection->addStoreFilter($this->getStoreFilter($product)); } + return $collection; } @@ -422,7 +426,7 @@ public function save($product = null) if (is_array($data)) { $productIds = array_keys($data); Mage::getResourceModel('catalog/product_type_configurable') - ->saveProducts($this->getProduct($product)->getId(), $productIds); + ->saveProducts($this->getProduct($product), $productIds); } return $this; } diff --git a/app/code/core/Mage/Catalog/Model/Product/Type/Configurable/Attribute.php b/app/code/core/Mage/Catalog/Model/Product/Type/Configurable/Attribute.php index bfc376b4bf..15323ca7d9 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Type/Configurable/Attribute.php +++ b/app/code/core/Mage/Catalog/Model/Product/Type/Configurable/Attribute.php @@ -26,7 +26,7 @@ /** - * Catalog super product attribute model + * Catalog Configurable Product Attribute Model * * @category Mage * @package Mage_Catalog @@ -61,28 +61,21 @@ public function addPrice($priceData) } /** - * Retrieve label + * Retrieve attribute label * * @return string */ public function getLabel() { - if (is_null($this->getData('label')) && $this->getProductAttribute()) { - // If no attribute label seted - $this->setData('label', $this->getProductAttribute()->getFrontend()->getLabel()); + if ($this->getData('use_default') && $this->getProductAttribute()) { + return $this->getProductAttribute()->getStoreLabel(); + } else if (is_null($this->getData('label')) && $this->getProductAttribute()) { + $this->setData('label', $this->getProductAttribute()->getStoreLabel()); } return $this->getData('label'); } - /*protected function _afterLoad() - { - parent::_afterLoad(); - $this->_getResource()->loadLabel($this); - $this->_getResource()->loadPrices($this); - return $this; - }*/ - /** * After save process * diff --git a/app/code/core/Mage/Catalog/Model/Product/Type/Grouped.php b/app/code/core/Mage/Catalog/Model/Product/Type/Grouped.php index 5c5fa48e4a..b69f759aa0 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Type/Grouped.php +++ b/app/code/core/Mage/Catalog/Model/Product/Type/Grouped.php @@ -218,6 +218,7 @@ public function getAssociatedProductCollection($product = null) $collection = $this->getProduct($product)->getLinkInstance()->useGroupedLinks() ->getProductCollection() ->setFlag('require_stock_items', true) + ->setFlag('product_children', true) ->setIsStrongMode(); $collection->setProduct($this->getProduct($product)); return $collection; diff --git a/app/code/core/Mage/Catalog/Model/Product/Visibility.php b/app/code/core/Mage/Catalog/Model/Product/Visibility.php index 0a21ef168a..05e7e25e7f 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Visibility.php +++ b/app/code/core/Mage/Catalog/Model/Product/Visibility.php @@ -283,7 +283,7 @@ public function addValueSortToCollection($collection, $dir = 'asc') . " AND `{$valueTable2}`.`store_id`='{$collection->getStoreId()}'", array() ); - $valueExpr = new Zend_Db_Expr("IFNULL(`{$valueTable2}`.`value`, `{$valueTable1}`.`value`)"); + $valueExpr = new Zend_Db_Expr("IF(`{$valueTable2}`.`value_id`>0, `{$valueTable2}`.`value`, `{$valueTable1}`.`value`)"); } $collection->getSelect()->order($valueExpr . ' ' . $dir); diff --git a/app/code/core/Mage/Catalog/Model/Product/Website.php b/app/code/core/Mage/Catalog/Model/Product/Website.php index 68ee9b2b0a..d37af7653e 100644 --- a/app/code/core/Mage/Catalog/Model/Product/Website.php +++ b/app/code/core/Mage/Catalog/Model/Product/Website.php @@ -64,14 +64,6 @@ public function removeProducts($websiteIds, $productIds) { try { $this->_getResource()->removeProducts($websiteIds, $productIds); - Mage::getResourceModel('catalog/category')->refreshProductIndex( - array(), $productIds - ); - Mage::dispatchEvent('catalog_product_website_update', array( - 'website_ids' => $websiteIds, - 'product_ids' => $productIds, - 'action' => 'remove' - )); } catch (Exception $e) { Mage::throwException( @@ -92,14 +84,6 @@ public function addProducts($websiteIds, $productIds) { try { $this->_getResource()->addProducts($websiteIds, $productIds); - Mage::getResourceModel('catalog/category')->refreshProductIndex( - array(), $productIds - ); - Mage::dispatchEvent('catalog_product_website_update', array( - 'website_ids' => $websiteIds, - 'product_ids' => $productIds, - 'action' => 'add' - )); } catch (Exception $e) { Mage::throwException( diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Attribute.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Attribute.php index 61c7ab2121..9b5fdaae86 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Attribute.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Attribute.php @@ -37,6 +37,12 @@ class Mage_Catalog_Model_Resource_Eav_Attribute extends Mage_Eav_Model_Entity_At const SCOPE_GLOBAL = 1; const SCOPE_WEBSITE = 2; + const MODULE_NAME = 'Mage_Catalog'; + const ENTITY = 'catalog_eav_attribute'; + + protected $_eventPrefix = 'catalog_entity_attribute'; + protected $_eventObject = 'attribute'; + /** * Array with labels * @@ -44,6 +50,102 @@ class Mage_Catalog_Model_Resource_Eav_Attribute extends Mage_Eav_Model_Entity_At */ static protected $_labels = null; + protected function _construct() + { + $this->_init('catalog/attribute'); + } + + /** + * Processing object before save data + * + * @return Mage_Core_Model_Abstract + */ + protected function _beforeSave() + { + $this->setData('modulePrefix', self::MODULE_NAME); + if (isset($this->_origData['is_global'])) { + if (!isset($this->_data['is_global'])) { + Mage::throwException('0_o'); + } + if (($this->_data['is_global'] != $this->_origData['is_global']) + && $this->_getResource()->isUsedBySuperProducts($this)) { + Mage::throwException(Mage::helper('eav')->__('Scope must not be changed, because the attribute is used in configurable products.')); + } + } + if ($this->getFrontendInput() == 'price') { + if (!$this->getBackendModel()) { + $this->setBackendModel('catalog/product_attribute_backend_price'); + } + } + return parent::_beforeSave(); + } + + /** + * Processing object after save data + * + * @return Mage_Core_Model_Abstract + */ + protected function _afterSave() + { + /** + * Fix saving attribute in admin + */ + Mage::getSingleton('eav/config')->clear(); + return parent::_afterSave(); + } + + /** + * Init indexing process after attribute data commit + * + * @return Mage_Catalog_Model_Resource_Eav_Attribute + */ + protected function _afterSaveCommit() + { + parent::_afterSaveCommit(); + + Mage::getSingleton('index/indexer')->processEntityAction( + $this, self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE + ); + + return $this; + } + + /** + * Register indexing event before delete catalog eav attribute + * + * @return Mage_Catalog_Model_Resource_Eav_Attribute + */ + protected function _beforeDelete() + { + Mage::getSingleton('index/indexer')->logEvent( + $this, self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE + ); + return parent::_beforeDelete(); + } + + /** + * Init indexing process after catalog eav attribute delete commit + * + * @return Mage_Catalog_Model_Resource_Eav_Attribute + */ + protected function _afterDeleteCommit() + { + parent::_afterDeleteCommit(); + Mage::getSingleton('index/indexer')->indexEvents( + self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE + ); + } + + /** + * Return is attribute global + * + * @return integer + */ + public function getIsGlobal() + { + return $this->_getData('is_global'); + } + /** * Retrieve attribute is global scope flag * @@ -96,6 +198,9 @@ public function getStoreId() public function getApplyTo() { if ($this->getData('apply_to')) { + if (is_array($this->getData('apply_to'))) { + return $this->getData('apply_to'); + } return explode(',', $this->getData('apply_to')); } else { return array(); diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Abstract.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Abstract.php index dabb857bcb..eddff0a6cc 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Abstract.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Abstract.php @@ -83,9 +83,12 @@ protected function _getLoadAttributesSelect($object, $table) $storeId = $object->getStoreId(); } + $setId = $object->getAttributeSetId(); $select = $this->_read->select() - ->from(array('default' => $table)); - if ($setId = $object->getAttributeSetId()) { + ->from(array('default' => $table)) + ->where('default.'.$this->getEntityIdField().'=?', $object->getId()) + ->where('default.store_id=?', $this->getDefaultStoreId()); + if ($setId) { $select->join( array('set_table' => $this->getTable('eav/entity_attribute')), 'default.attribute_id=set_table.attribute_id AND ' @@ -94,19 +97,21 @@ protected function _getLoadAttributesSelect($object, $table) ); } - $joinCondition = 'main.attribute_id=default.attribute_id AND ' - . $this->_read->quoteInto('main.store_id=? AND ', intval($storeId)) - . $this->_read->quoteInto('main.'.$this->getEntityIdField() . '=?', $object->getId()); - - $select->joinLeft( - array('main' => $table), - $joinCondition, - array( - 'store_value_id' => 'value_id', - 'store_value' => 'value' - )) - ->where('default.'.$this->getEntityIdField() . '=?', $object->getId()) - ->where('default.store_id=?', $this->getDefaultStoreId()); + if ($storeId != $this->getDefaultStoreId()) { + $joinCondition = join(' AND ', array( + 'main.attribute_id=default.attribute_id', + $this->_read->quoteInto('main.store_id=?', intval($storeId)), + $this->_read->quoteInto('main.'.$this->getEntityIdField() . '=?', $object->getId()) + )); + $select->joinLeft( + array('main' => $table), + $joinCondition, + array( + 'store_value_id' => 'value_id', + 'store_value' => 'value' + ) + ); + } return $select; } @@ -114,126 +119,208 @@ protected function _getLoadAttributesSelect($object, $table) /** * Initialize attribute value for object * - * @param Varien_Object $object + * @param Mage_Catalog_Model_Abstract $object * @param array $valueRow * @return Mage_Eav_Model_Entity_Abstract */ protected function _setAttribteValue($object, $valueRow) { - parent::_setAttribteValue($object, $valueRow); - if ($attribute = $this->getAttribute($valueRow['attribute_id'])) { + $attribute = $this->getAttribute($valueRow['attribute_id']); + if ($attribute) { $attributeCode = $attribute->getAttributeCode(); - if (isset($valueRow['store_value'])) { + if (!empty($valueRow['store_value_id'])) { + $value = $valueRow['store_value']; + $valueId = $valueRow['store_value_id']; $object->setAttributeDefaultValue($attributeCode, $valueRow['value']); - $object->setData($attributeCode, $valueRow['store_value']); - $attribute->getBackend()->setValueId($valueRow['store_value_id']); + } else { + $value = $valueRow['value']; + $valueId = $valueRow['value_id']; } + $object->setData($attributeCode, $value); + $attribute->getBackend()->setValueId($valueId); } return $this; } /** - * Insert entity attribute value + * Insert or Update attribute data * - * Insert attribute value we do only for default store - * - * @param Varien_Object $object - * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute - * @param mixed $value - * @return Mage_Eav_Model_Entity_Abstract + * @param Mage_Catalog_Model_Abstract $object + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @param mixed $value + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Abstract */ - protected function _insertAttribute($object, $attribute, $value) + protected function _saveAttributeValue($object, $attribute, $value) { - $entityIdField = $attribute->getBackend()->getEntityIdField(); - $row = array( - $entityIdField => $object->getId(), - 'entity_type_id'=> $object->getEntityTypeId(), - 'attribute_id' => $attribute->getId(), - 'value' => $this->_prepareValueForSave($value, $attribute), - 'store_id' => $this->getDefaultStoreId() - ); + $write = $this->_getWriteAdapter(); + $storeId = Mage::app()->getStore($object->getStoreId())->getId(); + $table = $attribute->getBackend()->getTable(); - $fields = array(); - $bind = array(); - foreach ($row as $k => $v) { - $fields[] = $this->_getWriteAdapter()->quoteIdentifier($k); - $bind[':' . $k] = $v; + /** + * If we work in single store mode all values should be saved just + * for default store id + * In this case we clear all not default values + */ + if (Mage::app()->isSingleStoreMode()) { + $storeId = $this->getDefaultStoreId(); + $write->delete($table, join(' AND ', array( + $write->quoteInto('attribute_id=?', $attribute->getAttributeId()), + $write->quoteInto('entity_id=?', $object->getEntityId()), + $write->quoteInto('store_id<>?', $storeId) + ))); } - $sql = sprintf('INSERT IGNORE INTO %s (%s) VALUES(%s)', - $this->_getWriteAdapter()->quoteIdentifier($attribute->getBackend()->getTable()), - implode(',', $fields), - implode(',', array_keys($bind))); - - $this->_getWriteAdapter()->query($sql, $bind); - if (!$lastId = $this->_getWriteAdapter()->lastInsertId()) { - $select = $this->_getReadAdapter()->select() - ->from($attribute->getBackend()->getTable(), 'value_id') - ->where($entityIdField . '=?', $row[$entityIdField]) - ->where('entity_type_id=?', $row['entity_type_id']) - ->where('attribute_id=?', $row['attribute_id']) - ->where('store_id=?', $row['store_id']); - $lastId = $select->query()->fetchColumn(); - } - if ($object->getStoreId() != $this->getDefaultStoreId()) { - $this->_updateAttribute($object, $attribute, $lastId, $value); + $bind = array( + 'entity_type_id' => $attribute->getEntityTypeId(), + 'attribute_id' => $attribute->getAttributeId(), + 'store_id' => $storeId, + 'entity_id' => $object->getEntityId(), + 'value' => $this->_prepareValueForSave($value, $attribute) + ); + + if ($attribute->isScopeStore()) { + /** + * Update attribute value for store + */ + $write->insertOnDuplicate($table, $bind, array('value')); + } else if ($attribute->isScopeWebsite() && $storeId != $this->getDefaultStoreId()) { + /** + * Update attribute value for website + */ + $storeIds = $object->getWebsiteStoreIds(); + foreach ($storeIds as $storeId) { + $bind['store_id'] = $storeId; + $write->insertOnDuplicate($table, $bind, array('value')); + } + } else { + /** + * Update global attribute value + */ + $bind['store_id'] = $this->getDefaultStoreId(); + $write->insertOnDuplicate($table, $bind, array('value')); } + return $this; } /** - * Update entity attribute value + * Insert entity attribute value * * @param Varien_Object $object * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute - * @param mixed $valueId * @param mixed $value * @return Mage_Eav_Model_Entity_Abstract */ - protected function _updateAttribute($object, $attribute, $valueId, $value) + protected function _insertAttribute($object, $attribute, $value) { /** - * If we work in single store mode all values should be saved just - * for default store id - * In this case we clear all not default values + * save required attributes in global scope every time if store id different from default */ - if (Mage::app()->isSingleStoreMode()) { - $this->_getWriteAdapter()->delete( - $attribute->getBackend()->getTable(), - $this->_getWriteAdapter()->quoteInto('attribute_id=?', $attribute->getId()) . - $this->_getWriteAdapter()->quoteInto(' AND entity_id=?', $object->getId()) . - $this->_getWriteAdapter()->quoteInto(' AND store_id!=?', Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID) + $storeId = Mage::app()->getStore($object->getStoreId())->getId(); + if ($attribute->getIsRequired() && $this->getDefaultStoreId() != $storeId) { + $bind = array( + 'entity_type_id' => $attribute->getEntityTypeId(), + 'attribute_id' => $attribute->getAttributeId(), + 'store_id' => $this->getDefaultStoreId(), + 'entity_id' => $object->getEntityId(), + 'value' => $this->_prepareValueForSave($value, $attribute) ); + $this->_getWriteAdapter()->insertOnDuplicate($attribute->getBackend()->getTable(), $bind, array('value')); } + return $this->_saveAttributeValue($object, $attribute, $value); + +// $entityIdField = $attribute->getBackend()->getEntityIdField(); +// $row = array( +// $entityIdField => $object->getId(), +// 'entity_type_id'=> $object->getEntityTypeId(), +// 'attribute_id' => $attribute->getId(), +// 'value' => $this->_prepareValueForSave($value, $attribute), +// 'store_id' => $this->getDefaultStoreId() +// ); +// +// $fields = array(); +// $bind = array(); +// foreach ($row as $k => $v) { +// $fields[] = $this->_getWriteAdapter()->quoteIdentifier($k); +// $bind[':' . $k] = $v; +// } +// +// $sql = sprintf('INSERT IGNORE INTO %s (%s) VALUES(%s)', +// $this->_getWriteAdapter()->quoteIdentifier($attribute->getBackend()->getTable()), +// implode(',', $fields), +// implode(',', array_keys($bind))); +// +// $this->_getWriteAdapter()->query($sql, $bind); +// if (!$lastId = $this->_getWriteAdapter()->lastInsertId()) { +// $select = $this->_getReadAdapter()->select() +// ->from($attribute->getBackend()->getTable(), 'value_id') +// ->where($entityIdField . '=?', $row[$entityIdField]) +// ->where('entity_type_id=?', $row['entity_type_id']) +// ->where('attribute_id=?', $row['attribute_id']) +// ->where('store_id=?', $row['store_id']); +// $lastId = $select->query()->fetchColumn(); +// } +// if ($object->getStoreId() != $this->getDefaultStoreId()) { +// $this->_updateAttribute($object, $attribute, $lastId, $value); +// } +// return $this; + } - /** - * Update attribute value for store - */ - if ($attribute->isScopeStore()) { - $this->_updateAttributeForStore($object, $attribute, $value, $object->getStoreId()); - } - - /** - * Update attribute value for website - */ - elseif ($attribute->isScopeWebsite()) { - if ($object->getStoreId() == 0) { - $this->_updateAttributeForStore($object, $attribute, $value, $object->getStoreId()); - } else { - if (is_array($object->getWebsiteStoreIds())) { - foreach ($object->getWebsiteStoreIds() as $storeId) { - $this->_updateAttributeForStore($object, $attribute, $value, $storeId); - } - } - } - } - else { - $this->_getWriteAdapter()->update($attribute->getBackend()->getTable(), - array('value' => $this->_prepareValueForSave($value, $attribute)), - 'value_id='.(int)$valueId - ); - } - return $this; + /** + * Update entity attribute value + * + * @param Varien_Object $object + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @param mixed $valueId + * @param mixed $value + * @return Mage_Eav_Model_Entity_Abstract + */ + protected function _updateAttribute($object, $attribute, $valueId, $value) + { + return $this->_saveAttributeValue($object, $attribute, $value); +// +// /** +// * If we work in single store mode all values should be saved just +// * for default store id +// * In this case we clear all not default values +// */ +// if (Mage::app()->isSingleStoreMode()) { +// $this->_getWriteAdapter()->delete( +// $attribute->getBackend()->getTable(), +// $this->_getWriteAdapter()->quoteInto('attribute_id=?', $attribute->getId()) . +// $this->_getWriteAdapter()->quoteInto(' AND entity_id=?', $object->getId()) . +// $this->_getWriteAdapter()->quoteInto(' AND store_id!=?', Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID) +// ); +// } +// +// /** +// * Update attribute value for store +// */ +// if ($attribute->isScopeStore()) { +// $this->_updateAttributeForStore($object, $attribute, $value, $object->getStoreId()); +// } +// +// /** +// * Update attribute value for website +// */ +// elseif ($attribute->isScopeWebsite()) { +// if ($object->getStoreId() == 0) { +// $this->_updateAttributeForStore($object, $attribute, $value, $object->getStoreId()); +// } else { +// if (is_array($object->getWebsiteStoreIds())) { +// foreach ($object->getWebsiteStoreIds() as $storeId) { +// $this->_updateAttributeForStore($object, $attribute, $value, $storeId); +// } +// } +// } +// } +// else { +// $this->_getWriteAdapter()->update($attribute->getBackend()->getTable(), +// array('value' => $this->_prepareValueForSave($value, $attribute)), +// 'value_id='.(int)$valueId +// ); +// } +// return $this; } /** @@ -342,6 +429,12 @@ protected function _deleteAttributes($object, $table, $info) return $this; } + /** + * Retrieve Object instance with original data + * + * @param Varien_Object $object + * @return Varien_Object + */ protected function _getOrigObject($object) { $className = get_class($object); @@ -352,45 +445,6 @@ protected function _getOrigObject($object) return $origObject; } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - protected function _collectOrigData($object) { $this->loadAllAttributes($object); @@ -441,4 +495,82 @@ protected function _collectOrigData($object) return $data; } + /** + * Check is attribute value empty + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @param mixed $value + * @return bool + */ + protected function _isAttributeValueEmpty(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $value) + { + return $value === false; + } + + /** + * Prepare value for save + * + * @param mixed $value + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @return mixed + */ + protected function _prepareValueForSave($value, Mage_Eav_Model_Entity_Attribute_Abstract $attribute) + { + $type = $attribute->getBackendType(); + if (($type == 'int' || $type == 'decimal' || $type == 'datetime') && $value === '') { + return null; + } + if ($type == 'decimal') { + return Mage::app()->getLocale()->getNumber($value); + } + return $value; + } + + /** + * Retrieve attribute's raw value from DB. + * + * @param int $entityId + * @param int|string $attribute atrribute's id or code + * @param int|Mage_Core_Model_Store $store + * @return bool|string + */ + public function getAttributeRawValue($entityId, $attribute, $store) + { + $result = ''; + $attribute = $this->getAttribute($attribute); + /* @var $attribute Mage_Catalog_Model_Entity_Attribute */ + if ($attribute) { + /* @var $select Zend_Db_Select */ + $select = $this->_read->select(); + + $attrTable = $attribute->getBackend()->getTable(); + $isStatic = $attribute->getBackend()->isStatic(); + $attrField = $isStatic ? $attributeCode : 'value'; + $select->from(array('default_value' => $attrTable), array()) + ->where('default_value.' . $this->getEntityIdField() . ' = ?', $entityId); + + if ($isStatic) { + $select->from('', $attrField); + } else { + $select->where('default_value.attribute_id = ?', $attribute->getId()) + ->where('default_value.store_id = 0'); + + if ($store instanceof Mage_Core_Model_Store) { + $store = $store->getId(); + } + + $joinCondition = $this->_read->quoteInto('store_value.entity_id = ?', $entityId); + $joinCondition .= ' AND ' . $this->_read->quoteInto('store_value.attribute_id = ?', $attribute->getId()); + $joinCondition .= ' AND ' . $this->_read->quoteInto('store_value.store_id = ?', $store); + + $select->joinLeft(array('store_value' => $attrTable), + $joinCondition, + array('IFNULL(store_value.' . $attrField . ', default_value.' . $attrField . ')') + ); + } + return $this->_read->fetchOne($select); + } + + return false; + } } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Attribute.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Attribute.php new file mode 100644 index 0000000000..cd577777d9 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Attribute.php @@ -0,0 +1,52 @@ +getApplyTo(); + if (is_array($applyTo)) { + $object->setApplyTo(implode(',', $applyTo)); + } + return parent::_beforeSave($object); + } + + +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category.php index 5d7b029516..e72ad9fa0c 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category.php @@ -136,7 +136,18 @@ protected function _beforeDelete(Varien_Object $object) array('children_count'=>new Zend_Db_Expr('`children_count`-'.$childDecrease)), $this->_getWriteAdapter()->quoteInto('entity_id IN(?)', $parentIds) ); + $this->deleteChildren($object); + return $this; + } + /** + * Delete children categories of specific category + * + * @param Varien_Object $object + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category + */ + public function deleteChildren(Varien_Object $object) + { /** * Recursion use a lot of memmory, that why we run one request for delete children */ @@ -240,6 +251,12 @@ protected function _savePath($object) return $this; } + /** + * Get maximum position of child categories by specific tree path + * + * @param string $path + * @return int + */ protected function _getMaxPosition($path) { $select = $this->getReadConnection()->select(); @@ -256,24 +273,22 @@ protected function _getMaxPosition($path) } /** - * Save category products + * Save category products relation * - * @param Mage_Catalog_Model_Category $category - * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category + * @param Mage_Catalog_Model_Category $category + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category */ protected function _saveCategoryProducts($category) { $category->setIsChangedProductList(false); - + $id = $category->getId(); /** * new category-product relationships - * */ $products = $category->getPostedProducts(); /** * Example re-save category - * */ if (is_null($products)) { return $this; @@ -281,7 +296,6 @@ protected function _saveCategoryProducts($category) /** * old category-product relationships - * */ $oldProducts = $category->getProductsPosition(); @@ -290,60 +304,46 @@ protected function _saveCategoryProducts($category) /** * Find product ids which are presented in both arrays - * + * and saved before (check $oldProducts array) */ $update = array_intersect_key($products, $oldProducts); - - /** - * Use for update just products with changed position - * - */ $update = array_diff_assoc($update, $oldProducts); + $adapter = $this->_getWriteAdapter(); + /** * Delete products from category - * */ if (!empty($delete)) { - $cond = join(' AND ', array( - $this->_getWriteAdapter()->quoteInto('product_id IN(?)', array_keys($delete)), - $this->_getWriteAdapter()->quoteInto('category_id=?', $category->getId()) - )); - $this->_getWriteAdapter()->delete($this->_categoryProductTable, $cond); + $cond = $adapter->quoteInto('product_id IN(?) AND ', array_keys($delete)) + . $adapter->quoteInto('category_id=?', $id); + $adapter->delete($this->_categoryProductTable, $cond); } /** * Add products to category - * */ if (!empty($insert)) { $data = array(); foreach ($insert as $productId => $position) { $data[] = array( - 'category_id' => $category->getId(), + 'category_id' => $id, 'product_id' => (int)$productId, 'position' => (int)$position ); } - - $this->_getWriteAdapter() - ->insertMultiple($this->_categoryProductTable, $data); + $adapter->insertMultiple($this->_categoryProductTable, $data); } /** * Update product positions in category - * */ if (!empty($update)) { foreach ($update as $productId => $position) { - $where = join(' AND ', array( - $this->_getWriteAdapter()->quoteInto('category_id=?', (int)$category->getId()), - $this->_getWriteAdapter()->quoteInto('product_id=?', (int)$productId) - )); - $bind = array( - 'position' => (int)$position - ); - $this->_getWriteAdapter()->update($this->_categoryProductTable, $bind, $where); + $where = $adapter->quoteInto('category_id=?', (int)$id) + . $adapter->quoteInto('product_id=?', (int)$productId); + $bind = array('position' => (int)$position); + $adapter->update($this->_categoryProductTable, $bind, $where); } } @@ -356,48 +356,18 @@ protected function _saveCategoryProducts($category) } if (!empty($insert) || !empty($update) || !empty($delete)) { + $category->setIsChangedProductList(true); - $categoryIds = explode('/', $category->getPath()); - $this->refreshProductIndex($categoryIds); + /** + * Moved to index + */ + //$categoryIds = explode('/', $category->getPath()); + //$this->refreshProductIndex($categoryIds); } return $this; } - /** - * Get store identifiers where category is presented - * - * @deprecated after 1.3.2.2 moved to model - * @param Mage_Catalog_Model_Category $category - * @return array - */ - public function getStoreIds($category) - { - if (!$category->getId()) { - return array(); - } - - $nodes = array(); - foreach ($category->getPathIds() as $id) { - $nodes[] = $id; - } - - $stores = array(); - $storeCollection = Mage::getModel('core/store')->getCollection()->loadByCategoryIds($nodes); - foreach ($storeCollection as $store) { - $stores[$store->getId()] = $store->getId(); - } - - $entityStoreId = $category->getStoreId(); - if (!in_array($entityStoreId, $stores)) { - array_unshift($stores, $entityStoreId); - } - if (!in_array(0, $stores)) { - array_unshift($stores, 0); - } - return $stores; - } - /** * Get positions of associated to category products * @@ -430,45 +400,6 @@ public function getChildrenCount($categoryId) return $child; } - /** - * Move category to another parent - * - * @param int $categoryId - * @param int $newParentId - * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category - */ - public function move($categoryId, $newParentId) - { - $category = Mage::getModel('catalog/category')->load($categoryId); - $oldParent = $category->getParentCategory(); - $newParent = Mage::getModel('catalog/category')->load($newParentId); - - $childrenCount = $this->getChildrenCount($category->getId()) + 1; - - // update children count of new parents - $parentIds = explode('/', $newParent->getPath()); - $this->_getWriteAdapter()->update( - $this->getEntityTable(), - array('children_count' => new Zend_Db_Expr("`children_count` + {$childrenCount}")), - $this->_getWriteAdapter()->quoteInto('entity_id IN (?)', $parentIds) - ); - - // update children count of old parents - $parentIds = explode('/', $oldParent->getPath()); - $this->_getWriteAdapter()->update( - $this->getEntityTable(), - array('children_count' => new Zend_Db_Expr("`children_count` - {$childrenCount}")), - $this->_getWriteAdapter()->quoteInto('entity_id IN (?)', $parentIds) - ); - - // update parent id - $this->_getWriteAdapter()->query("UPDATE - {$this->getEntityTable()} SET parent_id = {$newParent->getId()} - WHERE entity_id = {$categoryId}"); - - return $this; - } - /** * Check if category id exist * @@ -528,7 +459,7 @@ public function getChildrenAmount($category, $isActiveFlag = true) array() ) ->where('m.path like ?', $category->getPath() . '/%') - ->where('(IFNULL(c.value, d.value) = ?)', $isActiveFlag); + ->where('(IF(c.value_id>0, c.value, d.value) = ?)', $isActiveFlag); return $this->_getReadAdapter()->fetchOne($select); } @@ -552,372 +483,85 @@ protected function _getIsActiveAttributeId() return $this->_isActiveAttributeId; } + public function findWhereAttributeIs($entityIdsFilter, $attribute, $expectedValue) + { + $select = $this->_getReadAdapter()->select() + ->from($attribute->getBackend()->getTable(), array('entity_id')) + ->where('attribute_id = ?', $attribute->getId()) + ->where('value = ?', $expectedValue) + ->where('entity_id in (?)', $entityIdsFilter); + + return $this->_getReadAdapter()->fetchCol($select); + } + /** - * Refresh Category Product Index for Store Root Catgory + * Get products count in category * - * @param array|int $productIds - * @param array|int $storeIds - * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category + * @param unknown_type $category + * @return unknown */ - protected function _refreshRootCategoryProductIndex($productIds = array(), $storeIds = array()) + public function getProductCount($category) { - if (is_numeric($storeIds)) { - $storeIds = array($storeIds); - } - elseif (!is_array($storeIds) || empty($storeIds)) { - $storeIds = array(); - foreach (Mage::app()->getStores() as $store) { - $storeIds[] = $store->getId(); - } - } - - /** - * Prepare visibility and status attributes information - */ - $status = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'status'); - $visibility = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'visibility'); - $statusTable = $status->getBackend()->getTable(); - $visibilityTable = $visibility->getBackend()->getTable(); + $productTable =Mage::getSingleton('core/resource')->getTableName('catalog/category_product'); - $indexTable = $this->getTable('catalog/category_product_index'); + $select = $this->getReadConnection()->select(); + $select->from( + array('main_table'=>$productTable), + array(new Zend_Db_Expr('COUNT(main_table.product_id)')) + ) + ->where('main_table.category_id = ?', $category->getId()) + ->group('main_table.category_id'); - foreach ($storeIds as $storeId) { - $store = Mage::app()->getStore($storeId); - $categoryId = $store->getRootCategoryId(); + $counts =$this->getReadConnection()->fetchOne($select); - $select = $this->_getWriteAdapter()->select() - ->from(array('e' => $this->getTable('catalog/product')), null) - ->joinLeft( - array('i' => $indexTable), - 'e.entity_id=i.product_id AND i.category_id=' . (int)$categoryId - . ' AND i.store_id=' . (int) $storeId, - array()) - ->joinInner( - array('pw' => $this->getTable('catalog/product_website')), - 'e.entity_id=pw.product_id AND pw.website_id=' . (int)$store->getWebsiteId(), - array()) - ->join( - array('t_v_default' => $visibilityTable), - 't_v_default.entity_id=e.entity_id' - . ' AND t_v_default.attribute_id=' . (int)$visibility->getAttributeId() - . ' AND t_v_default.store_id=0', - array()) - ->joinLeft( - array('t_v' => $visibilityTable), - 't_v.entity_id=e.entity_id' - . ' AND t_v.attribute_id=' . (int)$visibility->getAttributeId() - . ' AND t_v.store_id='. (int)$storeId, - array()) - ->join( - array('t_s_default' => $statusTable), - 't_s_default.entity_id=e.entity_id' - . ' AND t_s_default.attribute_id=' . (int)$status->getAttributeId() - . ' AND t_s_default.store_id=0', - array()) - ->joinLeft( - array('t_s' => $statusTable), - 't_s.entity_id=e.entity_id' - . ' AND t_s.attribute_id=' . (int)$status->getAttributeId() - . ' AND t_s.store_id='. (int)$storeId, - array()) - ->where('i.product_id IS NULL') - ->where('IFNULL(t_s.value, t_s_default.value)=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); + return intval($counts); + } - $select->columns(new Zend_Db_Expr($categoryId)); - $select->columns('e.entity_id'); - $select->columns(new Zend_Db_Expr(0)); - $select->columns(new Zend_Db_Expr(0)); - $select->columns(new Zend_Db_Expr($storeId)); - $select->columns(new Zend_Db_Expr('IFNULL(t_v.value, t_v_default.value)')); + /** + * Retrieve categories + * + * @param integer $parent + * @param integer $recursionLevel + * @param boolean|string $sorted + * @param boolean $asCollection + * @param boolean $toLoad + * @return Varien_Data_Tree_Node_Collection|Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection + */ + public function getCategories($parent, $recursionLevel = 0, $sorted=false, $asCollection=false, $toLoad=true) + { + $tree = Mage::getResourceModel('catalog/category_tree'); + /** @var $tree Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Tree */ + $nodes = $tree->loadNode($parent) + ->loadChildren($recursionLevel) + ->getChildren(); - if (!empty($productIds)) { - $select->where('e.entity_id IN(?)', $productIds); - } + $tree->addCollectionData(null, $sorted, $parent, $toLoad, true); - $this->_getWriteAdapter()->query($select->insertFromSelect($indexTable)); + if ($asCollection) { + return $tree->getCollection(); } - return $this; + return $nodes; } /** - * Rebuild associated products index + * Return parent categories of category * - * @param array $categoryIds - * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category + * @param Mage_Catalog_Model_Category $category + * @return array */ - public function refreshProductIndex($categoryIds = array(), $productIds = array(), $storeIds = array()) + public function getParentCategories($category) { - /** - * Prepare visibility and status attributes information - */ - $statusAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'status'); - $visibilityAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'visibility'); - $statusAttributeId = $statusAttribute->getId(); - $visibilityAttributeId = $visibilityAttribute->getId(); - $statusTable = $statusAttribute->getBackend()->getTable(); - $visibilityTable = $visibilityAttribute->getBackend()->getTable(); - - /** - * Select categories data - */ - $select = $this->_getReadAdapter()->select() - ->from($this->getTable('catalog/category')) - ->order('level') - ->order('path'); - - if (is_array($categoryIds) && !empty($categoryIds)) { - $select->where('entity_id IN (?)', $categoryIds); - } elseif (is_numeric($categoryIds)) { - $select->where('entity_id=?', $categoryIds); - } - - $categories = $this->_getWriteAdapter()->fetchAll($select); - - $storesCondition = ''; - if (!empty($storeIds)) { - $storesCondition = $this->_getWriteAdapter()->quoteInto( - ' AND s.store_id IN (?)', $storeIds - ); - } - - /** - * Get information about stores root categories - */ - $stores = $this->_getWriteAdapter()->fetchAll(" - SELECT - s.store_id, s.website_id, c.path AS root_path - FROM - {$this->getTable('core/store')} AS s, - {$this->getTable('core/store_group')} AS sg, - {$this->getTable('catalog/category')} AS c - WHERE - sg.group_id=s.group_id - AND c.entity_id=sg.root_category_id - {$storesCondition} - "); - - $indexTable = $this->getTable('catalog/category_product_index'); - - foreach ($stores as $storeData) { - $storeId = $storeData['store_id']; - $websiteId = $storeData['website_id']; - $rootPath = $storeData['root_path']; - - $productCondition = ''; - if (!empty($productIds)) { - $productCondition = $this->_getWriteAdapter()->quoteInto( - ' AND product_id IN (?)', $productIds - ); - } - $insProductCondition = str_replace('product_id', 'cp.product_id', $productCondition); - - foreach ($categories as $category) { - $categoryId = $category['entity_id']; - $path = $category['path']; - - $this->_getWriteAdapter()->delete( - $indexTable, - 'category_id='.$categoryId. ' AND store_id='.$storeId.$productCondition - ); - - if (strpos($path.'/', $rootPath.'/') === false) { - continue; - } - - $query = "INSERT INTO {$indexTable} - (`category_id`, `product_id`, `position`, `is_parent`, `store_id`, `visibility`) - SELECT - {$categoryId}, - cp.product_id, - cp.position, - MAX({$categoryId}=cp.category_id) as is_parent, - {$storeId}, - IFNULL(t_v.value, t_v_default.value) - FROM - {$this->getTable('catalog/category_product')} AS cp - INNER JOIN {$this->getTable('catalog/product_website')} AS pw - ON pw.product_id=cp.product_id AND pw.website_id={$websiteId} - INNER JOIN {$visibilityTable} AS `t_v_default` - ON (t_v_default.entity_id = cp.product_id) - AND (t_v_default.attribute_id='{$visibilityAttributeId}') - AND t_v_default.store_id=0 - LEFT JOIN {$visibilityTable} AS `t_v` - ON (t_v.entity_id = cp.product_id) - AND (t_v.attribute_id='{$visibilityAttributeId}') - AND (t_v.store_id='{$storeId}') - INNER JOIN {$statusTable} AS `t_s_default` - ON (t_s_default.entity_id = cp.product_id) - AND (t_s_default.attribute_id='{$statusAttributeId}') - AND t_s_default.store_id=0 - LEFT JOIN {$statusTable} AS `t_s` - ON (t_s.entity_id = cp.product_id) - AND (t_s.attribute_id='{$statusAttributeId}') - AND (t_s.store_id='{$storeId}') - WHERE category_id IN( - SELECT entity_id FROM {$this->getTable('catalog/category')} - WHERE entity_id = {$category['entity_id']} OR path LIKE '{$path}/%') - AND (IFNULL(t_s.value, t_s_default.value)=".Mage_Catalog_Model_Product_Status::STATUS_ENABLED.") - {$insProductCondition} - GROUP BY product_id - ORDER BY is_parent desc"; - - $this->_getWriteAdapter()->query($query); - } - - $this->_refreshRootCategoryProductIndex($productIds, array($storeId)); - } - return $this; - } - - public function findWhereAttributeIs($entityIdsFilter, $attribute, $expectedValue) - { - $select = $this->_getReadAdapter()->select() - ->from($attribute->getBackend()->getTable(), array('entity_id')) - ->where('attribute_id = ?', $attribute->getId()) - ->where('value = ?', $expectedValue) - ->where('entity_id in (?)', $entityIdsFilter); - - return $this->_getReadAdapter()->fetchCol($select); - } - - /** - * Get products count in category - * - * @param unknown_type $category - * @return unknown - */ - public function getProductCount($category) - { - $productTable =Mage::getSingleton('core/resource')->getTableName('catalog/category_product'); - - $select = $this->getReadConnection()->select(); - $select->from( - array('main_table'=>$productTable), - array(new Zend_Db_Expr('COUNT(main_table.product_id)')) - ) - ->where('main_table.category_id = ?', $category->getId()) - ->group('main_table.category_id'); - - $counts =$this->getReadConnection()->fetchOne($select); - - return intval($counts); - } - - - - - - - - - /** - * Deprecated since 1.1.7 - * - * @param Varien_Object $object - * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category - */ - protected function _saveCountChidren($object) - { - $chidren = $object->getChildren(); - if (strlen($chidren)>0) { - $chidrenCount = count(explode(',', $chidren)); - } else { - $chidrenCount = 0; - } - $this->_getWriteAdapter()->update($this->getEntityTable(), - array('children_count'=>$chidrenCount), - $this->_getWriteAdapter()->quoteInto('entity_id=?', $object->getId()) - ); - - return $this; - } - - /** - * Deprecated - * - * @param Varien_Object $object - * @return unknown - */ - protected function _saveInStores(Varien_Object $object) - { - if (!$object->getMultistoreSaveFlag()) { - $stores = $object->getStoreIds(); - foreach ($stores as $storeId) { - if ($object->getStoreId() != $storeId) { - $newObject = clone $object; - $newObject->setStoreId($storeId) - ->setMultistoreSaveFlag(true) - ->save(); - } - } - } - return $this; - } - - /** - * Deprecated - */ - protected function _updateCategoryPath($category, $path) - { - return $this; - if ($category->getNotUpdateDepends()) { - return $this; - } - foreach ($path as $pathItem) { - if ($pathItem->getId()>1 && $category->getId() != $pathItem->getId()) { - $category = Mage::getModel('catalog/category') - ->load($pathItem->getId()) - ->save(); - } - } - return $this; - } - - /** - * Retrieve categories - * - * @param integer $parent - * @param integer $recursionLevel - * @param boolean|string $sorted - * @param boolean $asCollection - * @param boolean $toLoad - * @return Varien_Data_Tree_Node_Collection|Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection - */ - public function getCategories($parent, $recursionLevel = 0, $sorted=false, $asCollection=false, $toLoad=true) - { - $tree = Mage::getResourceModel('catalog/category_tree'); - /** @var $tree Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Tree */ - $nodes = $tree->loadNode($parent) - ->loadChildren($recursionLevel) - ->getChildren(); - - $tree->addCollectionData(null, $sorted, $parent, $toLoad, true); - - if ($asCollection) { - return $tree->getCollection(); - } - return $nodes; - } - - /** - * Return parent categories of category - * - * @param Mage_Catalog_Model_Category $category - * @return array - */ - public function getParentCategories($category) - { - $pathIds = array_reverse(explode(',', $category->getPathInStore())); - $categories = Mage::getResourceModel('catalog/category_collection') - ->setStore(Mage::app()->getStore()) - ->addAttributeToSelect('name') - ->addAttributeToSelect('url_key') - ->addFieldToFilter('entity_id', array('in'=>$pathIds)) - ->addFieldToFilter('is_active', 1) - ->load() - ->getItems(); - return $categories; - } + $pathIds = array_reverse(explode(',', $category->getPathInStore())); + $categories = Mage::getResourceModel('catalog/category_collection') + ->setStore(Mage::app()->getStore()) + ->addAttributeToSelect('name') + ->addAttributeToSelect('url_key') + ->addFieldToFilter('entity_id', array('in'=>$pathIds)) + ->addFieldToFilter('is_active', 1) + ->load() + ->getItems(); + return $categories; + } /** * Enter description here... @@ -963,7 +607,7 @@ public function getChildren($category, $recursive = true) "c.attribute_id = '{$attributeId}' AND c.store_id = '{$category->getStoreId()}' AND c.entity_id = m.entity_id", array() ) - ->where('(IFNULL(c.value, d.value) = ?)', '1') + ->where('(IF(c.value_id>0, c.value, d.value) = ?)', '1') ->where('path LIKE ?', "{$category->getPath()}/%"); if (!$recursive) { $select->where('level <= ?', $category->getLevel() + 1); @@ -975,9 +619,6 @@ public function getChildren($category, $recursive = true) } return $categoriesIds; - -// $this->_getTree()->load(); -// return $this->_getTree()->getChildren($category->getId(), false); } /** @@ -993,18 +634,6 @@ public function getAllChildren($category) $children = array_merge($myId, $children); return $children; - -// $this->_getTree()->load(); -// $children = $this->_getTree()->getChildren($category->getId()); -// -// $myId = array($category->getId()); -// if (is_array($children)) { -// $children = array_merge($myId, $children); -// } else { -// $children = $myId; -// } -// -// return $children; } /** @@ -1023,14 +652,6 @@ public function isInRootCategoryList($category) ->where('entity_id = ?', $category->getId()) ->where(new Zend_Db_Expr("path LIKE ({$innerSelect->__toString()})")); return (bool) $this->_getReadAdapter()->fetchOne($select); - -// $tree = $this->_getTree(); -// $tree->load(); -// $children = $tree->getChildren(Mage::app()->getStore()->getRootCategoryId(), true); -// if (!in_array($category->getId(), $children)) { -// return false; -// } -// return true; } /** @@ -1051,4 +672,481 @@ public function isForbiddenToDelete($categoryId) } return false; } + + /** + * Get category path value by its id + * + * @param int $categoryId + * @return string + */ + public function getCategoryPathById($categoryId) + { + $select = $this->getReadConnection()->select(); + $select->from($this->getEntityTable(), array('path')) + ->where('entity_id = ?', $categoryId); + return $this->getReadConnection()->fetchOne($select); + } + + /** + * Move category to another parent node + * + * @param Mage_Catalog_Model_Category $category + * @param Mage_Catalog_Model_Category $newParent + * @param null|int $afterCategoryId + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category + */ + public function changeParent(Mage_Catalog_Model_Category $category, Mage_Catalog_Model_Category $newParent, $afterCategoryId=null) + { + $childrenCount = $this->getChildrenCount($category->getId()) + 1; + $table = $this->getEntityTable(); + $adapter = $this->_getWriteAdapter(); + $categoryId = $category->getId(); + /** + * Decrease children count for all old category parent categories + */ + $sql = "UPDATE {$table} SET children_count=children_count-{$childrenCount} WHERE entity_id IN(?)"; + $adapter->query($adapter->quoteInto($sql, $category->getParentIds())); + /** + * Increase children count for new category parents + */ + $sql = "UPDATE {$table} SET children_count=children_count+{$childrenCount} WHERE entity_id IN(?)"; + $adapter->query($adapter->quoteInto($sql, $newParent->getPathIds())); + + $position = $this->_processPositions($category, $newParent, $afterCategoryId); + + $newPath = $newParent->getPath().'/'.$category->getId(); + $newLevel= $newParent->getLevel()+1; + $levelDisposition = $newLevel - $category->getLevel(); + + /** + * Update children nodes path + */ + $sql = "UPDATE {$table} SET + `path` = REPLACE(`path`, '{$category->getPath()}/', '{$newPath}/'), + `level` = `level` + {$levelDisposition} + WHERE ". $adapter->quoteInto('path LIKE ?', $category->getPath().'/%'); + $adapter->query($sql); + /** + * Update moved category data + */ + $data = array('path' => $newPath, 'level' => $newLevel, + 'position'=>$position, 'parent_id'=>$newParent->getId()); + $adapter->update($table, $data, $adapter->quoteInto('entity_id=?', $category->getId())); + return $this; + } + + /** + * Process positions of old parent category children and new parent category children. + * Get position for moved category + * + * @param Mage_Catalog_Model_Category $category + * @param Mage_Catalog_Model_Category $newParent + * @param null|int $afterCategoryId + * @return int + */ + protected function _processPositions($category, $newParent, $afterCategoryId) + { + $table = $this->getEntityTable(); + $adapter = $this->_getWriteAdapter(); + + $sql = "UPDATE {$table} SET `position`=`position`-1 WHERE " + . $adapter->quoteInto('parent_id=? AND ', $category->getParentId()) + . $adapter->quoteInto('position>?', $category->getPosition()); + $adapter->query($sql); + + /** + * Prepare position value + */ + if ($afterCategoryId) { + $sql = "SELECT `position` FROM {$table} WHERE entity_id=?"; + $position = $adapter->fetchOne($adapter->quoteInto($sql, $afterCategoryId)); + + $sql = "UPDATE {$table} SET `position`=`position`+1 WHERE " + . $adapter->quoteInto('parent_id=? AND ', $newParent->getId()) + . $adapter->quoteInto('position>?', $position); + $adapter->query($sql); + } elseif ($afterCategoryId !== null) { + $position = 0; + $sql = "UPDATE {$table} SET `position`=`position`+1 WHERE " + . $adapter->quoteInto('parent_id=? AND ', $newParent->getId()) + . $adapter->quoteInto('position>?', $position); + $adapter->query($sql); + } else { + $sql = "SELECT MIN(`position`) FROM {$table} WHERE parent_id=?"; + $position = $adapter->fetchOne($adapter->quoteInto($sql, $newParent->getId())); + } + $position+=1; + + return $position; + } + + + + + + + + + + + + + + + + + /** + * @deprecated + * @param Varien_Object $object + * @return unknown + */ + protected function _saveInStores(Varien_Object $object) + { + if (!$object->getMultistoreSaveFlag()) { + $stores = $object->getStoreIds(); + foreach ($stores as $storeId) { + if ($object->getStoreId() != $storeId) { + $newObject = clone $object; + $newObject->setStoreId($storeId) + ->setMultistoreSaveFlag(true) + ->save(); + } + } + } + return $this; + } + + /** + * @deprecated + */ + protected function _updateCategoryPath($category, $path) + { + return $this; + if ($category->getNotUpdateDepends()) { + return $this; + } + foreach ($path as $pathItem) { + if ($pathItem->getId()>1 && $category->getId() != $pathItem->getId()) { + $category = Mage::getModel('catalog/category') + ->load($pathItem->getId()) + ->save(); + } + } + return $this; + } + + + /** + * @deprecated since 1.1.7 + * @param Varien_Object $object + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category + */ + protected function _saveCountChidren($object) + { + $chidren = $object->getChildren(); + if (strlen($chidren)>0) { + $chidrenCount = count(explode(',', $chidren)); + } else { + $chidrenCount = 0; + } + $this->_getWriteAdapter()->update($this->getEntityTable(), + array('children_count'=>$chidrenCount), + $this->_getWriteAdapter()->quoteInto('entity_id=?', $object->getId()) + ); + + return $this; + } + + /** + * Get store identifiers where category is presented + * + * @deprecated after 1.3.2.2 moved to model + * @param Mage_Catalog_Model_Category $category + * @return array + */ + public function getStoreIds($category) + { + if (!$category->getId()) { + return array(); + } + + $nodes = array(); + foreach ($category->getPathIds() as $id) { + $nodes[] = $id; + } + + $stores = array(); + $storeCollection = Mage::getModel('core/store')->getCollection()->loadByCategoryIds($nodes); + foreach ($storeCollection as $store) { + $stores[$store->getId()] = $store->getId(); + } + + $entityStoreId = $category->getStoreId(); + if (!in_array($entityStoreId, $stores)) { + array_unshift($stores, $entityStoreId); + } + if (!in_array(0, $stores)) { + array_unshift($stores, 0); + } + return $stores; + } + + /** + * Move category to another parent + * @deprecated after 1.4.0.0-Alpha we are using changeParent method + * @param int $categoryId + * @param int $newParentId + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category + */ + public function move($categoryId, $newParentId) + { + $category = Mage::getModel('catalog/category')->load($categoryId); + $oldParent = $category->getParentCategory(); + $newParent = Mage::getModel('catalog/category')->load($newParentId); + + $childrenCount = $this->getChildrenCount($category->getId()) + 1; + + // update children count of new parents + $parentIds = explode('/', $newParent->getPath()); + $this->_getWriteAdapter()->update( + $this->getEntityTable(), + array('children_count' => new Zend_Db_Expr("`children_count` + {$childrenCount}")), + $this->_getWriteAdapter()->quoteInto('entity_id IN (?)', $parentIds) + ); + + // update children count of old parents + $parentIds = explode('/', $oldParent->getPath()); + $this->_getWriteAdapter()->update( + $this->getEntityTable(), + array('children_count' => new Zend_Db_Expr("`children_count` - {$childrenCount}")), + $this->_getWriteAdapter()->quoteInto('entity_id IN (?)', $parentIds) + ); + + // update parent id + $this->_getWriteAdapter()->query("UPDATE + {$this->getEntityTable()} SET parent_id = {$newParent->getId()} + WHERE entity_id = {$categoryId}"); + + return $this; + } + + /** + * Rebuild associated products index + * + * @deprecated after 1.4.0.0-Alpha, functionality moved to Mage_Catalog_Model_Category_Indexer_Produxt + * @param array $categoryIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category + */ + public function refreshProductIndex($categoryIds = array(), $productIds = array(), $storeIds = array()) + { + /** + * Prepare visibility and status attributes information + */ + $statusAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'status'); + $visibilityAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'visibility'); + $statusAttributeId = $statusAttribute->getId(); + $visibilityAttributeId = $visibilityAttribute->getId(); + $statusTable = $statusAttribute->getBackend()->getTable(); + $visibilityTable = $visibilityAttribute->getBackend()->getTable(); + + /** + * Select categories data + */ + $select = $this->_getReadAdapter()->select() + ->from($this->getTable('catalog/category')) + ->order('level') + ->order('path'); + + if (is_array($categoryIds) && !empty($categoryIds)) { + $select->where('entity_id IN (?)', $categoryIds); + } elseif (is_numeric($categoryIds)) { + $select->where('entity_id=?', $categoryIds); + } + + $categories = $this->_getWriteAdapter()->fetchAll($select); + + $storesCondition = ''; + if (!empty($storeIds)) { + $storesCondition = $this->_getWriteAdapter()->quoteInto( + ' AND s.store_id IN (?)', $storeIds + ); + } + + /** + * Get information about stores root categories + */ + $stores = $this->_getWriteAdapter()->fetchAll(" + SELECT + s.store_id, s.website_id, c.path AS root_path + FROM + {$this->getTable('core/store')} AS s, + {$this->getTable('core/store_group')} AS sg, + {$this->getTable('catalog/category')} AS c + WHERE + sg.group_id=s.group_id + AND c.entity_id=sg.root_category_id + {$storesCondition} + "); + + $indexTable = $this->getTable('catalog/category_product_index'); + + foreach ($stores as $storeData) { + $storeId = $storeData['store_id']; + $websiteId = $storeData['website_id']; + $rootPath = $storeData['root_path']; + + $productCondition = ''; + if (!empty($productIds)) { + $productCondition = $this->_getWriteAdapter()->quoteInto( + ' AND product_id IN (?)', $productIds + ); + } + $insProductCondition = str_replace('product_id', 'cp.product_id', $productCondition); + + foreach ($categories as $category) { + $categoryId = $category['entity_id']; + $path = $category['path']; + + $this->_getWriteAdapter()->delete( + $indexTable, + 'category_id='.$categoryId. ' AND store_id='.$storeId.$productCondition + ); + + if (strpos($path.'/', $rootPath.'/') === false) { + continue; + } + + $query = "INSERT INTO {$indexTable} + (`category_id`, `product_id`, `position`, `is_parent`, `store_id`, `visibility`) + SELECT + {$categoryId}, + cp.product_id, + cp.position, + MAX({$categoryId}=cp.category_id) as is_parent, + {$storeId}, + IF(t_v.value_id>0, t_v.value, t_v_default.value) + FROM + {$this->getTable('catalog/category_product')} AS cp + INNER JOIN {$this->getTable('catalog/product_website')} AS pw + ON pw.product_id=cp.product_id AND pw.website_id={$websiteId} + INNER JOIN {$visibilityTable} AS `t_v_default` + ON (t_v_default.entity_id = cp.product_id) + AND (t_v_default.attribute_id='{$visibilityAttributeId}') + AND t_v_default.store_id=0 + LEFT JOIN {$visibilityTable} AS `t_v` + ON (t_v.entity_id = cp.product_id) + AND (t_v.attribute_id='{$visibilityAttributeId}') + AND (t_v.store_id='{$storeId}') + INNER JOIN {$statusTable} AS `t_s_default` + ON (t_s_default.entity_id = cp.product_id) + AND (t_s_default.attribute_id='{$statusAttributeId}') + AND t_s_default.store_id=0 + LEFT JOIN {$statusTable} AS `t_s` + ON (t_s.entity_id = cp.product_id) + AND (t_s.attribute_id='{$statusAttributeId}') + AND (t_s.store_id='{$storeId}') + WHERE category_id IN( + SELECT entity_id FROM {$this->getTable('catalog/category')} + WHERE entity_id = {$category['entity_id']} OR path LIKE '{$path}/%') + AND (IF(t_s.value_id>0, t_s.value, t_s_default.value)=".Mage_Catalog_Model_Product_Status::STATUS_ENABLED.") + {$insProductCondition} + GROUP BY product_id + ORDER BY is_parent desc"; + + $this->_getWriteAdapter()->query($query); + } + + $this->_refreshRootCategoryProductIndex($productIds, array($storeId)); + } + return $this; + } + + /** + * Refresh Category Product Index for Store Root Catgory + * + * @deprecated after 1.4.0.0-Alpha, functionality moved to Mage_Catalog_Model_Category_Indexer_Produxt + * @param array|int $productIds + * @param array|int $storeIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category + */ + protected function _refreshRootCategoryProductIndex($productIds = array(), $storeIds = array()) + { + if (is_numeric($storeIds)) { + $storeIds = array($storeIds); + } + elseif (!is_array($storeIds) || empty($storeIds)) { + $storeIds = array(); + foreach (Mage::app()->getStores() as $store) { + $storeIds[] = $store->getId(); + } + } + + /** + * Prepare visibility and status attributes information + */ + $status = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'status'); + $visibility = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'visibility'); + $statusTable = $status->getBackend()->getTable(); + $visibilityTable = $visibility->getBackend()->getTable(); + + $indexTable = $this->getTable('catalog/category_product_index'); + + foreach ($storeIds as $storeId) { + $store = Mage::app()->getStore($storeId); + $categoryId = $store->getRootCategoryId(); + + $select = $this->_getWriteAdapter()->select() + ->from(array('e' => $this->getTable('catalog/product')), null) + ->joinLeft( + array('i' => $indexTable), + 'e.entity_id=i.product_id AND i.category_id=' . (int)$categoryId + . ' AND i.store_id=' . (int) $storeId, + array()) + ->joinInner( + array('pw' => $this->getTable('catalog/product_website')), + 'e.entity_id=pw.product_id AND pw.website_id=' . (int)$store->getWebsiteId(), + array()) + ->join( + array('t_v_default' => $visibilityTable), + 't_v_default.entity_id=e.entity_id' + . ' AND t_v_default.attribute_id=' . (int)$visibility->getAttributeId() + . ' AND t_v_default.store_id=0', + array()) + ->joinLeft( + array('t_v' => $visibilityTable), + 't_v.entity_id=e.entity_id' + . ' AND t_v.attribute_id=' . (int)$visibility->getAttributeId() + . ' AND t_v.store_id='. (int)$storeId, + array()) + ->join( + array('t_s_default' => $statusTable), + 't_s_default.entity_id=e.entity_id' + . ' AND t_s_default.attribute_id=' . (int)$status->getAttributeId() + . ' AND t_s_default.store_id=0', + array()) + ->joinLeft( + array('t_s' => $statusTable), + 't_s.entity_id=e.entity_id' + . ' AND t_s.attribute_id=' . (int)$status->getAttributeId() + . ' AND t_s.store_id='. (int)$storeId, + array()) + ->where('i.product_id IS NULL') + ->where('IF(t_s.value_id>0, t_s.value, t_s_default.value)=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); + + $select->columns(new Zend_Db_Expr($categoryId)); + $select->columns('e.entity_id'); + $select->columns(new Zend_Db_Expr(0)); + $select->columns(new Zend_Db_Expr(0)); + $select->columns(new Zend_Db_Expr($storeId)); + $select->columns(new Zend_Db_Expr('IF(t_v.value_id>0, t_v.value, t_v_default.value)')); + + if (!empty($productIds)) { + $select->where('e.entity_id IN(?)', $productIds); + } + + $this->_getWriteAdapter()->query($select->insertFromSelect($indexTable)); + } + return $this; + } + } \ No newline at end of file diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Attribute/Collection.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Attribute/Collection.php new file mode 100644 index 0000000000..df088c031a --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Attribute/Collection.php @@ -0,0 +1,57 @@ +getSelect()->from(array('main_table' => $this->getResource()->getMainTable())) + ->where('main_table.entity_type_id=?', Mage::getModel('eav/entity')->setType('catalog_category')->getTypeId()) + ->join( + array('additional_table' => $this->getTable('catalog/eav_attribute')), + 'additional_table.attribute_id=main_table.attribute_id' + ); + return $this; + } + + /** + * Specify attribute entity type filter + * + * @param int $typeId + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Attribute_Collection + */ + public function setEntityTypeFilter($typeId) + { + return $this; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Collection.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Collection.php index 5a81d4fac9..ae6976d6c7 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Collection.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Collection.php @@ -168,14 +168,18 @@ public function getProductStoreId() } /** - * Enter description here... + * Load collection * - * @param boolean $printQuery - * @param boolean $logQuery + * @param bool $printQuery + * @param bool $logQuery * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection */ public function load($printQuery = false, $logQuery = false) { + if ($this->isLoaded()) { + return $this; + } + if ($this->_loadWithProductCount) { $this->addAttributeToSelect('all_children'); $this->addAttributeToSelect('is_anchor'); @@ -192,8 +196,6 @@ public function load($printQuery = false, $logQuery = false) /** * Load categories product count - * - * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection */ protected function _loadProductCount() { diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat.php index ac91194822..080a26487b 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat.php @@ -55,6 +55,13 @@ class Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat extends Mage_Core_Mod protected $_isRebuilt = null; + /** + * array with root category id per store + * + * @var array + */ + protected $_storesRootCategories; + protected function _construct() { $this->_init('catalog/category_flat', 'entity_id'); @@ -504,9 +511,9 @@ protected function _createTable($store) } } $_tableSql .= $this->_columnsSql; - $_tableSql .= "KEY `CATEGORY_FLAT_CATEGORY_ID` (`entity_id`), - KEY `CATEGORY_FLAT_STORE_ID` (`store_id`), - KEY `path` (`path`), + $_tableSql .= "PRIMARY KEY (`entity_id`), + KEY `IDX_STORE` (`store_id`), + KEY `IDX_PATH` (`path`), KEY `IDX_LEVEL` (`level`), CONSTRAINT `FK_CATEGORY_FLAT_CATEGORY_ID_STORE_{$store}` FOREIGN KEY (`entity_id`) REFERENCES `{$this->getTable('catalog/category')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, @@ -700,7 +707,7 @@ protected function _getAttributeTypeValues($type, $entityIds, $store_id) ->joinLeft( array('store' => $this->getTable('catalog/category') . '_' . $type), '`store`.entity_id = `default`.entity_id AND `store`.attribute_id = `default`.attribute_id AND `store`.store_id = ' . $store_id, - array('value' => new Zend_Db_Expr('IFNULL(`store`.value, `default`.value)')) + array('value' => new Zend_Db_Expr('IF(`store`.`value_id`>0, `store`.`value`, `default`.`value`)')) ) ->where('`default`.entity_id IN (?)', $entityIds) ->where('`default`.store_id = ?', 0); @@ -739,130 +746,87 @@ protected function _deleteTable($stores) } /** - * Synchronize flat data with eav model. + * Synchronize flat data with eav model for category * - * @param Mage_Catalog_Model_Category $category - * @param null|string $action + * @param Varien_Object $category * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat */ - protected function _synchronize($category, $action = null) + protected function _synchronize($category) { - if (is_null($action)) { - $select = $this->_getWriteAdapter()->select() - ->from($this->getMainStoreTable($category->getStoreId()), 'entity_id') - ->where('entity_id = ?', $category->getEntityId()); - if ($result = $this->_getWriteAdapter()->fetchOne($select)) { - $action = 'update'; - } else { - $action = 'insert'; - } - } - - if ($action == 'update') { - // update - $this->_getWriteAdapter()->update( - $this->getMainStoreTable($category->getStoreId()), - $this->_prepareDataForAllFields($category), - $this->_getWriteAdapter()->quoteInto('entity_id = ?', $category->getEntityId()) - ); - } elseif ($action == 'insert') { - // insert - $this->_getWriteAdapter()->insert( - $this->getMainStoreTable($category->getStoreId()), - $this->_prepareDataForAllFields($category) - ); - } - return $this; - } - - /** - * Synchronize flat data with eav model when category was moved. - * - * @param string $prevParentPath - * @param string $parentPath - * @param array $storeIds - * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat - */ - protected function _move($prevParentPath, $parentPath) - { - $_staticFields = array( - 'parent_id', - 'path', - 'level', - 'position', - 'children_count', - 'updated_at' - ); - $select = $this->_getWriteAdapter()->select() - ->from($this->getTable('core/store'), 'store_id'); - $stores = $this->_getWriteAdapter()->fetchAll($select); - foreach ($stores as $store) { - $update = "UPDATE {$this->getMainStoreTable($store['store_id'])}, {$this->getTable('catalog/category')} SET"; - foreach ($_staticFields as $field) { - $update .= " {$this->getMainStoreTable($store['store_id'])}.".$field."={$this->getTable('catalog/category')}.".$field.","; - } - $update = substr($update, 0, -1); - $update .= " WHERE {$this->getMainStoreTable($store['store_id'])}.entity_id = {$this->getTable('catalog/category')}.entity_id AND " . - "({$this->getTable('catalog/category')}.path like '$parentPath/%' OR " . - "{$this->getTable('catalog/category')}.path like '$prevParentPath/%')"; - $this->_getWriteAdapter()->query($update); - } + $table = $this->getMainStoreTable($category->getStoreId()); + $data = $this->_prepareDataForAllFields($category); + $this->_getWriteAdapter()->insertOnDuplicate($table, $data); return $this; } /** * Synchronize flat data with eav model. * - * @param Mage_Catalog_Model_Category|array $category + * @param Mage_Catalog_Model_Category|int $category * @param array $storeIds * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat */ public function synchronize($category = null, $storeIds = array()) { if (is_null($category)) { - $storesCondition = ''; - if (!empty($storeIds)) { - $storesCondition = $this->_getWriteAdapter()->quoteInto( - ' AND s.store_id IN (?)', $storeIds - ); + if (empty($storeIds)) { + $storeIds = null; } - $stores = $this->_getWriteAdapter()->fetchAll(" - SELECT - s.store_id, s.website_id, c.path AS root_path, sg.root_category_id - FROM - {$this->getTable('core/store')} AS s, - {$this->getTable('core/store_group')} AS sg, - {$this->getTable('catalog/category')} AS c - WHERE - sg.group_id=s.group_id - AND c.entity_id=sg.root_category_id - {$storesCondition} - "); + $stores = $this->getStoresRootCategories($storeIds); + $storesObjects = array(); - foreach ($stores as $store) { -// if ($this->_getWriteAdapter()->showTableStatus($this->getMainStoreTable($store['store_id']))) { -// $this->_getWriteAdapter()->truncate($this->getMainStoreTable($store['store_id'])); -// } - $_store = new Varien_Object(); - $_store->setData('id', $store['store_id']) - ->setData('root_category_id', $store['root_category_id']); + foreach ($stores as $storeId => $rootCategoryId) { + $_store = new Varien_Object(array( + 'store_id' => $storeId, + 'root_category_id' => $rootCategoryId + )); + $_store->setIdFieldName('store_id'); $storesObjects[] = $_store; } + $this->rebuild($storesObjects); - } elseif ($category instanceof Mage_Catalog_Model_Category) { - $categoriesIds = array($category->getId()); + } else if ($category instanceof Mage_Catalog_Model_Category) { + $categoryId = $category->getId(); foreach ($category->getStoreIds() as $storeId) { if ($storeId == 0) { continue; } - $attributesData = $this->_getAttributeValues($categoriesIds, $storeId); - $data = new Varien_Object(); - $data->setData($category->getData()) - ->addData($attributesData[$category->getId()]) - ->setData('store_id', $storeId); + + $attributeValues = $this->_getAttributeValues($categoryId, $storeId); + $data = new Varien_Object($category->getData()); + $data->addData($attributeValues[$categoryId]) + ->setStoreId($storeId); $this->_synchronize($data); } + } else if (is_numeric($category)) { + $write = $this->_getWriteAdapter(); + $select = $write->select() + ->from($this->getTable('catalog/category')) + ->where('entity_id=?', $category); + Mage::log($select->assemble()); + $row = $write->fetchRow($select); + if (!$row) { + return $this; + } + + $stores = $this->getStoresRootCategories(); + $path = explode('/', $row['path']); + Mage::log($path); + foreach ($stores as $storeId => $rootCategoryId) { + if (in_array($rootCategoryId, $path)) { + $attributeValues = $this->_getAttributeValues($category, $storeId); + $data = new Varien_Object($row); + $data->addData($attributeValues[$category]) + ->setStoreId($storeId); + Mage::log($data->debug()); + $this->_synchronize($data); + } else { + $where = $write->quoteInto('entity_id=?', $category); + $write->delete($this->getMainStoreTable($storeId), $where); + } + } } + return $this; } @@ -872,6 +836,80 @@ public function removeStores($stores) return $this; } + /** + * Synchronize flat category data after move by affected category ids + * + * @param array $affectedCategoryIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat + */ + public function move(array $affectedCategoryIds) + { + $write = $this->_getWriteAdapter(); + $select = $write->select() + ->from($this->getTable('catalog/category'), array('entity_id', 'path')) + ->where('entity_id IN(?)', $affectedCategoryIds); + $pairs = $write->fetchPairs($select); + + $pathCond = array($write->quoteInto('entity_id IN(?)', $affectedCategoryIds)); + $parentIds = array(); + + foreach ($pairs as $path) { + $pathCond[] = $write->quoteInto('path LIKE ?', $path . '/%'); + $parentIds = array_merge($parentIds, explode('/', $path)); + } + + $stores = $this->getStoresRootCategories(); + $where = join(' OR ', $pathCond); + $lastId = 0; + while (true) { + $select = $write->select() + ->from($this->getTable('catalog/category')) + ->where('entity_id>?', $lastId) + ->where($where) + ->order('entity_id') + ->limit(500); + $rowSet = $write->fetchAll($select); + + if (!$rowSet) { + break; + } + + $addStores = array(); + $remStores = array(); + + foreach ($rowSet as &$row) { + $lastId = $row['entity_id']; + $path = explode('/', $row['path']); + foreach ($stores as $storeId => $rootCategoryId) { + if (in_array($rootCategoryId, $path)) { + $addStores[$storeId][$row['entity_id']] = $row; + } else { + $remStores[$storeId][] = $row['entity_id']; + } + } + } + + // remove + foreach ($remStores as $storeId => $categoryIds) { + $where = $write->quoteInto('entity_id IN(?)', $categoryIds); + $write->delete($this->getMainStoreTable($storeId), $where); + } + + // add/update + foreach ($addStores as $storeId => $storeCategoryIds) { + $attributeValues = $this->_getAttributeValues(array_keys($storeCategoryIds), $storeId); + foreach ($storeCategoryIds as $row) { + $data = new Varien_Object($row); + $data->addData($attributeValues[$row['entity_id']]) + ->setStoreId($storeId); + $this->_synchronize($data); + } + } + } + + return $this; + } + /** * Synchronize flat data with eav after moving category * @@ -880,7 +918,7 @@ public function removeStores($stores) * @param integer $parentId * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat */ - public function move($categoryId, $prevParentId, $parentId) + public function moveold($categoryId, $prevParentId, $parentId) { $_staticFields = array( 'parent_id', @@ -1175,4 +1213,40 @@ public function getAnchorsAbove(array $filterIds, $storeId = 0) return $this->_getReadAdapter()->fetchCol($select); } + + /** + * Retrieve array with root category id per store + * + * @param int|array $storeIds result limitation + * @return array + */ + public function getStoresRootCategories($storeIds = null) + { + if (is_null($this->_storesRootCategories)) { + $select = $this->_getWriteAdapter()->select() + ->from(array('cs' => $this->getTable('core/store')), array('store_id')) + ->join( + array('csg' => $this->getTable('core/store_group')), + 'csg.group_id = cs.group_id', + array('root_category_id')) + ->where('cs.store_id <> ?', 0); + $this->_storesRootCategories = $this->_getWriteAdapter()->fetchPairs($select); + } + + if (!is_null($storeIds)) { + if (!is_array($storeIds)) { + $storeIds = array($storeIds); + } + + $stores = array(); + foreach ($this->_storesRootCategories as $storeId => $rootId) { + if (in_array($storeId, $storeIds)) { + $stores[$storeId] = $rootId; + } + } + return $stores; + } + + return $this->_storesRootCategories; + } } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat/Collection.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat/Collection.php index 7d7f277b32..8c512bd293 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat/Collection.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Flat/Collection.php @@ -326,4 +326,18 @@ public function addOrderField($field) $this->setOrder('main_table.' . $field, 'ASC'); return $this; } + + /** + * Set collection page start and records to show + * + * @param integer $pageNum + * @param integer $pageSize + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat_Collection + */ + public function setPage($pageNum, $pageSize) + { + $this->setCurPage($pageNum) + ->setPageSize($pageSize); + return $this; + } } \ No newline at end of file diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Indexer/Product.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Indexer/Product.php new file mode 100644 index 0000000000..d2a4c86462 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Indexer/Product.php @@ -0,0 +1,509 @@ +_init('catalog/category_product_index', 'category_id'); + $this->_categoryTable = $this->getTable('catalog/category'); + $this->_categoryProductTable = $this->getTable('catalog/category_product'); + $this->_productWebsiteTable = $this->getTable('catalog/product_website'); + $this->_storeTable = $this->getTable('core/store'); + $this->_groupTable = $this->getTable('core/store_group'); + } + + /** + * Process product save. + * Method is responsible for index support + * when product was saved and assigned categories was changed. + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Indexer_Product + */ + public function catalogProductSave(Mage_Index_Model_Event $event) + { + $productId = $event->getEntityPk(); + $data = $event->getNewData(); + + /** + * Check if category ids were updated + */ + if (!isset($data['category_ids'])) { + return $this; + } + + /** + * Select relations to categories + */ + $select = $this->_getWriteAdapter()->select() + ->from(array('cp' => $this->_categoryProductTable), 'category_id') + ->joinInner(array('ce' => $this->_categoryTable), 'ce.entity_id=cp.category_id', 'path') + ->where('cp.product_id=?', $productId); + + /** + * Get information about product categories + */ + $categories = $this->_getWriteAdapter()->fetchPairs($select); + $categoryIds = array(); + $allCategoryIds = array(); + + foreach ($categories as $id=>$path) { + $categoryIds[] = $id; + $allCategoryIds = array_merge($allCategoryIds, explode('/', $path)); + } + $allCategoryIds = array_unique($allCategoryIds); + $allCategoryIds = array_diff($allCategoryIds, $categoryIds); + + /** + * Delete previous index data + */ + $this->_getWriteAdapter()->delete( + $this->getMainTable(), + $this->_getWriteAdapter()->quoteInto('product_id=?', $productId) + ); + + $this->_refreshAnchorRelations($allCategoryIds, $productId); + $this->_refreshDirectRelations($categoryIds, $productId); + return $this; + } + + /** + * Process category index after category save + * + * @param Mage_Index_Model_Event $event + */ + public function catalogCategorySave(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + + /** + * Check if we have reindex category move results + */ + if (isset($data['affected_category_ids'])) { + $categoryIds = $event->getNewData('affected_category_ids'); + } else if (isset($data['products_was_changed'])) { + $categoryIds = array($event->getEntityPk()); + } else { + return; + } + + $select = $this->_getWriteAdapter()->select() + ->from($this->_categoryTable, 'path') + ->where('entity_id IN (?)', $categoryIds); + $paths = $this->_getWriteAdapter()->fetchCol($select); + $allCategoryIds = array(); + foreach ($paths as $path) { + $allCategoryIds = array_merge($allCategoryIds, explode('/', $path)); + } + $allCategoryIds = array_unique($allCategoryIds); + $this->_getWriteAdapter()->delete( + $this->getMainTable(), + $this->_getWriteAdapter()->quoteInto('category_id IN(?)', $allCategoryIds) + ); + + $allCategoryIds = array_diff($allCategoryIds, $categoryIds); + + $this->_refreshAnchorRelations($allCategoryIds); + $this->_refreshDirectRelations($categoryIds); + } + + /** + * Rebuild index for direct associations categories and products + * + * @param null|array $categoryIds + * @param null|array $productIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Indexer_Product + */ + protected function _refreshDirectRelations($categoryIds=null, $productIds=null) + { + $visibilityInfo = $this->_getVisibilityAttributeInfo(); + $statusInfo = $this->_getStatusAttributeInfo(); + + /** + * Insert direct relations + * product_ids (enabled filter) X category_ids X store_ids + * Validate store root category + */ + $isParent = new Zend_Db_Expr('1 AS is_parent'); + $select = $this->_getWriteAdapter()->select() + ->from(array('cp' => $this->_categoryProductTable), + array('category_id', 'product_id', 'position', $isParent)) + ->joinInner(array('pw' => $this->_productWebsiteTable), 'pw.product_id=cp.product_id', array()) + ->joinInner(array('g' => $this->_groupTable), 'g.website_id=pw.website_id', array()) + ->joinInner(array('s' => $this->_storeTable), 's.group_id=g.group_id', array('store_id')) + ->joinInner(array('rc' => $this->_categoryTable), 'rc.entity_id=g.root_category_id', array()) + ->joinInner( + array('ce'=>$this->_categoryTable), + 'ce.entity_id=cp.category_id AND ce.path LIKE CONCAT(rc.path, \'/%\')', + array()) + ->joinLeft( + array('dv'=>$visibilityInfo['table']), + "dv.entity_id=cp.product_id AND dv.attribute_id={$visibilityInfo['id']} AND dv.store_id=0", + array()) + ->joinLeft( + array('sv'=>$visibilityInfo['table']), + "sv.entity_id=cp.product_id AND sv.attribute_id={$visibilityInfo['id']} AND sv.store_id=s.store_id", + array('visibility' => 'IF(sv.value_id, sv.value, dv.value)')) + ->joinLeft( + array('ds'=>$statusInfo['table']), + "ds.entity_id=cp.product_id AND ds.attribute_id={$statusInfo['id']} AND ds.store_id=0", + array()) + ->joinLeft( + array('ss'=>$statusInfo['table']), + "ss.entity_id=cp.product_id AND ss.attribute_id={$statusInfo['id']} AND ss.store_id=s.store_id", + array()) + ->where('IF(ss.value_id, ss.value, ds.value)=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); + if ($categoryIds) { + $select->where('cp.category_id IN (?)', $categoryIds); + } + if ($productIds) { + $select->where('cp.product_id IN(?)', $productIds); + } + $sql = $select->insertFromSelect( + $this->getMainTable(), + array('category_id', 'product_id', 'position', 'is_parent', 'store_id', 'visibility'), + true + ); + $this->_getWriteAdapter()->query($sql); + return $this; + } + + /** + * Rebuild index for anchor categories and associated t child categories products + * + * @param null | array $categoryIds + * @param null | array $productIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Indexer_Product + */ + protected function _refreshAnchorRelations($categoryIds=null, $productIds=null) + { + $anchorInfo = $this->_getAnchorAttributeInfo(); + $visibilityInfo = $this->_getVisibilityAttributeInfo(); + $statusInfo = $this->_getStatusAttributeInfo(); + + /** + * Insert anchor categories relations + */ + $isParent = new Zend_Db_Expr('0'); + $position = new Zend_Db_Expr('0'); + $select = $this->_getReadAdapter()->select() + ->distinct(true) + ->from(array('ce' => $this->_categoryTable), array('entity_id')) + ->joinInner(array('cc' => $this->_categoryTable), 'cc.path LIKE CONCAT(ce.path, \'/%\')', array()) + ->joinInner(array('cp' => $this->_categoryProductTable), 'cp.category_id=cc.entity_id', array()) + ->joinInner( + array('pw' => $this->_productWebsiteTable), + 'pw.product_id=cp.product_id', + array('product_id', $position, $isParent) + ) + ->joinInner(array('g' => $this->_groupTable), 'g.website_id=pw.website_id', array()) + ->joinInner(array('s' => $this->_storeTable), 's.group_id=g.group_id', array('store_id')) + ->joinInner(array('rc' => $this->_categoryTable), 'rc.entity_id=g.root_category_id', array()) + ->joinLeft( + array('dca'=>$anchorInfo['table']), + "dca.entity_id=ce.entity_id AND dca.attribute_id={$anchorInfo['id']} AND dca.store_id=0", + array()) + ->joinLeft( + array('sca'=>$anchorInfo['table']), + "sca.entity_id=ce.entity_id AND sca.attribute_id={$anchorInfo['id']} AND sca.store_id=s.store_id", + array()) + ->joinLeft( + array('dv'=>$visibilityInfo['table']), + "dv.entity_id=pw.product_id AND dv.attribute_id={$visibilityInfo['id']} AND dv.store_id=0", + array()) + ->joinLeft( + array('sv'=>$visibilityInfo['table']), + "sv.entity_id=pw.product_id AND sv.attribute_id={$visibilityInfo['id']} AND sv.store_id=s.store_id", + array('visibility' => 'IF(sv.value_id, sv.value, dv.value)')) + ->joinLeft( + array('ds'=>$statusInfo['table']), + "ds.entity_id=pw.product_id AND ds.attribute_id={$statusInfo['id']} AND ds.store_id=0", + array()) + ->joinLeft( + array('ss'=>$statusInfo['table']), + "ss.entity_id=pw.product_id AND ss.attribute_id={$statusInfo['id']} AND ss.store_id=s.store_id", + array()) + /** + * Condition for anchor or root category (all products should be assigned to root) + */ + ->where('(ce.path LIKE CONCAT(rc.path, \'/%\') AND IF(sca.value_id, sca.value, dca.value)=1) OR ce.entity_id=rc.entity_id') + ->where('IF(ss.value_id, ss.value, ds.value)=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); + + if ($categoryIds) { + $select->where('ce.entity_id IN (?)', $categoryIds); + } + if ($productIds) { + $select->where('pw.product_id=?', $productIds); + } + + $sql = $select->insertFromSelect($this->getMainTable()); + Mage::log($sql); + $this->_getWriteAdapter()->query($sql); + return $this; + } + + /** + * Get is_anchor category attribute information + * + * @return array array('id' => $id, 'table'=>$table) + */ + protected function _getAnchorAttributeInfo() + { + $isAnchorAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_category', 'is_anchor'); + $info = array( + 'id' => $isAnchorAttribute->getId() , + 'table' => $isAnchorAttribute->getBackend()->getTable() + ); + return $info; + } + + /** + * Get visibility product attribute information + * + * @return array array('id' => $id, 'table'=>$table) + */ + protected function _getVisibilityAttributeInfo() + { + $visibilityAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'visibility'); + $info = array( + 'id' => $visibilityAttribute->getId() , + 'table' => $visibilityAttribute->getBackend()->getTable() + ); + return $info; + } + + /** + * Get status product attribute information + * + * @return array array('id' => $id, 'table'=>$table) + */ + protected function _getStatusAttributeInfo() + { + $statusAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'status'); + $info = array( + 'id' => $statusAttribute->getId() , + 'table' => $statusAttribute->getBackend()->getTable() + ); + return $info; + } + + /** + * Rebuild all index data + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Indexer_Product + */ + public function reindexAll() + { + /** + * Create temporary index table + */ + $this->cloneIndexTable(); + $idxTable = $this->getIdxTable(); + $idxAdapter = $this->_getIndexAdapter(); + $stores = $this->_getStoresInfo(); + /** + * Build index for each store + */ + foreach ($stores as $storeData) { + $storeId = $storeData['store_id']; + $websiteId = $storeData['website_id']; + $rootPath = $storeData['root_path']; + /** + * Prepare visibility for all enabled store products + */ + $enabledTable = $this->_prepareEnabledProductsVisibility($websiteId, $storeId); + /** + * Select information about anchor categories + */ + $anchorTable = $this->_prepareAnchorCategories($storeId, $rootPath); + /** + * Add relations between not anchor categories and products + */ + $sql = "INSERT INTO {$idxTable} + SELECT + cp.category_id, cp.product_id, cp.position, 1, {$storeId}, pv.visibility + FROM + {$this->_categoryProductTable} AS cp + INNER JOIN {$enabledTable} AS pv ON pv.product_id=cp.product_id + LEFT JOIN {$anchorTable} AS ac ON ac.category_id=cp.category_id + WHERE + ac.category_id IS NULL"; + $idxAdapter->query($sql); + /** + * Prepare anchor categories products + */ + $anchorProductsTable = $this->_resources->getTableName('tmp_category_index_anchor_products'); + $idxAdapter->query('DROP TABLE IF EXISTS ' . $anchorProductsTable); + $sql = "CREATE TABLE `{$anchorProductsTable}` ( + `category_id` int(10) unsigned NOT NULL DEFAULT '0', + `product_id` int(10) unsigned NOT NULL DEFAULT '0' + ) ENGINE=MyISAM"; + $idxAdapter->query($sql); + $sql = "SELECT + STRAIGHT_JOIN DISTINCT + ca.category_id, cp.product_id + FROM {$anchorTable} AS ca + INNER JOIN {$this->_categoryTable} AS ce + ON ce.path LIKE ca.path OR ce.entity_id = ca.category_id + INNER JOIN {$this->_categoryProductTable} AS cp + ON cp.category_id = ce.entity_id + INNER JOIN {$enabledTable} as pv + ON pv.product_id = cp.product_id"; + $this->insertFromSelect($sql, $anchorProductsTable, array('category_id' , 'product_id')); + /** + * Add anchor categories products to index + */ + $sql = "INSERT INTO {$idxTable} + SELECT + ap.category_id, ap.product_id, cp.position, + IF(cp.product_id, 1, 0), {$storeId}, pv.visibility + FROM + {$anchorProductsTable} AS ap + LEFT JOIN {$this->_categoryProductTable} AS cp + ON cp.category_id=ap.category_id AND cp.product_id=ap.product_id + INNER JOIN {$enabledTable} as pv + ON pv.product_id = ap.product_id"; + $idxAdapter->query($sql); + } + $this->syncData(); + $tmpTables = array( + $idxAdapter->quoteIdentifier($idxTable), + $idxAdapter->quoteIdentifier($enabledTable), + $idxAdapter->quoteIdentifier($anchorTable), + $idxAdapter->quoteIdentifier($anchorProductsTable) + ); + $idxAdapter->query('DROP TABLE IF EXISTS '.implode(',', $tmpTables)); + return $this; + } + + /** + * Get array with store|website|root_categry path information + * + * @return array + */ + protected function _getStoresInfo() + { + $stores = $this->_getReadAdapter()->fetchAll(" + SELECT + s.store_id, s.website_id, c.path AS root_path + FROM + {$this->getTable('core/store')} AS s, + {$this->getTable('core/store_group')} AS sg, + {$this->getTable('catalog/category')} AS c + WHERE + sg.group_id=s.group_id + AND c.entity_id=sg.root_category_id + "); + return $stores; + } + + /** + * Create temporary table with enabled products visibility info + * + * @return string temporary table name + */ + protected function _prepareEnabledProductsVisibility($websiteId, $storeId) + { + $statusAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'status'); + $visibilityAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_product', 'visibility'); + $statusAttributeId = $statusAttribute->getId(); + $visibilityAttributeId = $visibilityAttribute->getId(); + $statusTable = $statusAttribute->getBackend()->getTable(); + $visibilityTable = $visibilityAttribute->getBackend()->getTable(); + /** + * Prepare temporary table + */ + $tmpTable = $this->_resources->getTableName('tmp_category_index_enabled_products'); + $sql = 'DROP TABLE IF EXISTS ' . $tmpTable; + $this->_getIndexAdapter()->query($sql); + $sql = "CREATE TABLE {$tmpTable} ( + `product_id` int(10) unsigned NOT NULL DEFAULT '0', + `visibility` int(11) unsigned NOT NULL DEFAULT '0', + KEY `IDX_PRODUCT` (`product_id`) + ) ENGINE=MyISAM"; + $this->_getIndexAdapter()->query($sql); + $sql = "SELECT + pw.product_id AS product_id, + IF(pvs.value_id>0, pvs.value, pvd.value) AS visibility + FROM + {$this->_productWebsiteTable} AS pw + LEFT JOIN {$visibilityTable} AS pvd + ON pvd.entity_id=pw.product_id AND pvd.attribute_id={$visibilityAttributeId} AND pvd.store_id=0 + LEFT JOIN {$visibilityTable} AS pvs + ON pvs.entity_id=pw.product_id AND pvs.attribute_id={$visibilityAttributeId} AND pvs.store_id={$storeId} + LEFT JOIN {$statusTable} AS psd + ON psd.entity_id=pw.product_id AND psd.attribute_id={$statusAttributeId} AND psd.store_id=0 + LEFT JOIN {$statusTable} AS pss + ON pss.entity_id=pw.product_id AND pss.attribute_id={$statusAttributeId} AND pss.store_id={$storeId} + WHERE + pw.website_id={$websiteId} + AND IF(pss.value_id>0, pss.value, psd.value) = " . Mage_Catalog_Model_Product_Status::STATUS_ENABLED; + $this->insertFromSelect($sql, $tmpTable, array('product_id' , 'visibility')); + return $tmpTable; + } + + /** + * Create temporary table with list of anchor categories + * + * @param int $storeId + * @return string temporary table name + */ + protected function _prepareAnchorCategories($storeId, $rootPath) + { + $isAnchorAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_category', 'is_anchor'); + $anchorAttributeId = $isAnchorAttribute->getId(); + $anchorTable = $isAnchorAttribute->getBackend()->getTable(); + $tmpTable = $this->_resources->getTableName('tmp_category_index_anchor_categories'); + $sql = 'DROP TABLE IF EXISTS ' . $tmpTable; + $this->_getIndexAdapter()->query($sql); + $sql = "CREATE TABLE {$tmpTable} ( + `category_id` int(10) unsigned NOT NULL DEFAULT '0', + `path` varchar(255) CHARACTER SET utf8 NOT NULL DEFAULT '', + KEY `IDX_CATEGORY` (`category_id`) + ) ENGINE=MyISAM"; + $this->_getIndexAdapter()->query($sql); + $sql = "SELECT + ce.entity_id AS category_id, + concat(ce.path, '/%') AS path + FROM + {$this->_categoryTable} as ce + LEFT JOIN {$anchorTable} AS cad + ON cad.entity_id=ce.entity_id AND cad.attribute_id={$anchorAttributeId} AND cad.store_id=0 + LEFT JOIN {$anchorTable} AS cas + ON cas.entity_id=ce.entity_id AND cas.attribute_id={$anchorAttributeId} AND cas.store_id={$storeId} + WHERE + (IF(cas.value_id>0, cas.value, cad.value) = 1 AND ce.path LIKE '{$rootPath}/%') + OR ce.path='{$rootPath}'"; + $this->insertFromSelect($sql, $tmpTable, array('category_id' , 'path')); + return $tmpTable; + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Tree.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Tree.php index dc504829cd..b86afebca8 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Tree.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Category/Tree.php @@ -271,7 +271,7 @@ protected function _getInactiveItemIds($collection, $storeId) ->where('d.store_id = ?', 0) ->where('d.entity_id IN (?)', new Zend_Db_Expr($filter)) ->joinLeft(array('c'=>$table), "c.attribute_id = '{$attributeId}' AND c.store_id = '{$storeId}' AND c.entity_id = d.entity_id", array()) - ->where('IFNULL(c.value, d.value) = ?', 0); + ->where('IF(c.value_id>0, c.value, d.value) = ?', 0); return $this->_conn->fetchCol($select); } @@ -326,9 +326,12 @@ protected function _getDefaultCollection($sorted=false) $collection = Mage::getModel('catalog/category')->getCollection(); /* @var $collection Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection */ - $collection->addAttributeToSelect('name') - ->addAttributeToSelect('url_key') - ->addAttributeToSelect('is_active'); + $attributes = Mage::getConfig()->getNode('frontend/category/collection/attributes'); + if ($attributes) { + $attributes = $attributes->asArray(); + $attributes = array_keys($attributes); + } + $collection->addAttributeToSelect($attributes); if ($sorted) { if (is_string($sorted)) { @@ -552,7 +555,7 @@ protected function _createCollectionDataSelect($sorted = true, $optionalAttribut sprintf('`%1$s`.entity_id=e.entity_id AND `%1$s`.attribute_id=%2$d AND `%1$s`.entity_type_id=e.entity_type_id AND `%1$s`.store_id=%3$d', $storeTableAs, $attribute->getData('attribute_id'), $this->getStoreId() ), - array($attributeCode => new Zend_Db_Expr("IFNULL(`{$storeTableAs}`.value, `$defaultTableAs`.value)")) + array($attributeCode => new Zend_Db_Expr("IF(`{$storeTableAs}`.value>0, `{$storeTableAs}`.value, `$defaultTableAs`.value)")) ); } } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Collection/Abstract.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Collection/Abstract.php index db0b5c6b09..b8f9dc228b 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Collection/Abstract.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Collection/Abstract.php @@ -89,7 +89,7 @@ protected function _getLoadAttributesSelect($table, $attributeIds = array()) $joinCondition, array( 'store_value' => 'value', - 'value' => new Zend_Db_Expr('IFNULL(store.value, default.value)') + 'value' => new Zend_Db_Expr('IF(store.value_id>0, store.value, default.value)') ) ) ->where('default.entity_type_id=?', $this->getEntity()->getTypeId()) @@ -167,7 +167,7 @@ protected function _joinAttributeToSelect($method, $attribute, $tableAlias, $con ); $method = 'joinLeft'; - $fieldAlias = new Zend_Db_Expr("IFNULL($fieldAlias, $defFieldAlias)"); + $fieldAlias = new Zend_Db_Expr("IF($tableAlias.value_id>0, $fieldAlias, $defFieldAlias)"); $this->_joinAttributes[$fieldCode]['condition_alias'] = $fieldAlias; $this->_joinAttributes[$fieldCode]['attribute'] = $attribute; } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Config.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Config.php index 05e6c37fa0..8a1d75fa4d 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Config.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Config.php @@ -41,6 +41,8 @@ class Mage_Catalog_Model_Resource_Eav_Mysql4_Config extends Mage_Core_Model_Mysq */ protected $_entityTypeId; + protected $_storeId = null; + /** * Initialize connection * @@ -49,6 +51,32 @@ protected function _construct() { $this->_init('eav/attribute', 'attribute_id'); } + /** + * Set store id + * + * @param integer $storeId + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Config + */ + public function setStoreId($storeId) + { + $this->_storeId = $storeId; + return $this; + } + + /** + * Return store id. + * If is not set return current app store + * + * @return integer + */ + public function getStoreId() + { + if ($this->_storeId === null) { + return Mage::app()->getStore()->getId(); + } + return $this->_storeId; + } + /** * Retrieve catalog_product entity type id * @@ -72,9 +100,19 @@ public function getEntityTypeId() */ public function getAttributesUsedInListing() { $select = $this->_getReadAdapter()->select() - ->from($this->getTable('eav/attribute')) - ->where('entity_type_id=?', $this->getEntityTypeId()) - ->where('used_in_product_listing=?', 1); + ->from(array('main_table' => $this->getTable('eav/attribute'))) + ->join( + array('additional_table' => $this->getTable('catalog/eav_attribute')), + 'main_table.attribute_id = additional_table.attribute_id', + array() + ) + ->joinLeft( + array('al' => $this->getTable('eav/attribute_label')), + 'al.attribute_id = main_table.attribute_id AND al.store_id = ' . (int) $this->getStoreId(), + array('store_label' => new Zend_Db_Expr('IFNULL(al.value, main_table.frontend_label)')) + ) + ->where('main_table.entity_type_id=?', $this->getEntityTypeId()) + ->where('additional_table.used_in_product_listing=?', 1); return $this->_getReadAdapter()->fetchAll($select); } @@ -85,9 +123,19 @@ public function getAttributesUsedInListing() { */ public function getAttributesUsedForSortBy() { $select = $this->_getReadAdapter()->select() - ->from($this->getTable('eav/attribute')) - ->where('entity_type_id=?', $this->getEntityTypeId()) - ->where('used_for_sort_by=?', 1); + ->from(array('main_table' => $this->getTable('eav/attribute'))) + ->join( + array('additional_table' => $this->getTable('catalog/eav_attribute')), + 'main_table.attribute_id = additional_table.attribute_id', + array() + ) + ->joinLeft( + array('al' => $this->getTable('eav/attribute_label')), + 'al.attribute_id = main_table.attribute_id AND al.store_id = ' . (int) $this->getStoreId(), + array('store_label' => new Zend_Db_Expr('IFNULL(al.value, main_table.frontend_label)')) + ) + ->where('main_table.entity_type_id=?', $this->getEntityTypeId()) + ->where('additional_table.used_for_sort_by=?', 1); return $this->_getReadAdapter()->fetchAll($select); } } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Layer/Filter/Attribute.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Layer/Filter/Attribute.php new file mode 100644 index 0000000000..b1d50b6f15 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Layer/Filter/Attribute.php @@ -0,0 +1,109 @@ + + */ +class Mage_Catalog_Model_Resource_Eav_Mysql4_Layer_Filter_Attribute extends Mage_Core_Model_Mysql4_Abstract +{ + /** + * Initialize connection and define main table name + * + */ + protected function _construct() + { + $this->_init('catalog/product_index_eav', 'entity_id'); + } + + /** + * Apply attribute filter to product collection + * + * @param Mage_Catalog_Model_Layer_Filter_Attribute $filter + * @param int $value + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Layer_Filter_Attribute + */ + public function applyFilterToCollection($filter, $value) + { + $collection = $filter->getLayer()->getProductCollection(); + $attribute = $filter->getAttributeModel(); + $connection = $this->_getReadAdapter(); + $tableAlias = $attribute->getAttributeCode() . '_idx'; + $conditions = array( + "{$tableAlias}.entity_id = e.entity_id", + $connection->quoteInto("{$tableAlias}.attribute_id = ?", $attribute->getAttributeId()), + $connection->quoteInto("{$tableAlias}.store_id = ?", $collection->getStoreId()), + $connection->quoteInto("{$tableAlias}.value = ?", $value) + ); + + $collection->getSelect()->join( + array($tableAlias => $this->getMainTable()), + join(' AND ', $conditions), + array() + ); + + return $this; + } + + /** + * Retrieve array with products counts per attribute option + * + * @param Mage_Catalog_Model_Layer_Filter_Attribute $filter + * @return array + */ + public function getCount($filter) + { + // clone select from collection with filters + $select = clone $filter->getLayer()->getProductCollection()->getSelect(); + // reset columns, order and limitation conditions + $select->reset(Zend_Db_Select::COLUMNS); + $select->reset(Zend_Db_Select::ORDER); + $select->reset(Zend_Db_Select::LIMIT_COUNT); + $select->reset(Zend_Db_Select::LIMIT_OFFSET); + + $connection = $this->_getReadAdapter(); + $attribute = $filter->getAttributeModel(); + $tableAlias = $attribute->getAttributeCode() . '_idx'; + $conditions = array( + "{$tableAlias}.entity_id = e.entity_id", + $connection->quoteInto("{$tableAlias}.attribute_id = ?", $attribute->getAttributeId()), + $connection->quoteInto("{$tableAlias}.store_id = ?", $filter->getStoreId()), + ); + + $select + ->join( + array($tableAlias => $this->getMainTable()), + join(' AND ', $conditions), + array('value', 'count' => "COUNT({$tableAlias}.entity_id)")) + ->group("{$tableAlias}.value"); + + return $connection->fetchPairs($select); + } +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Layer/Filter/Price.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Layer/Filter/Price.php new file mode 100644 index 0000000000..77f83dc6f9 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Layer/Filter/Price.php @@ -0,0 +1,191 @@ + + */ +class Mage_Catalog_Model_Resource_Eav_Mysql4_Layer_Filter_Price extends Mage_Core_Model_Mysql4_Abstract +{ + /** + * Initialize connection and define main table name + * + */ + protected function _construct() + { + $this->_init('catalog/product_index_price', 'entity_id'); + } + + /** + * Retrieve joined price index table alias + * + * @return string + */ + protected function _getIndexTableAlias() + { + return 'price_index'; + } + + /** + * Retrieve clean select with joined price index table + * + * @param Mage_Catalog_Model_Layer_Filter_Price $filter + * @return Varien_Db_Select + */ + protected function _getSelect($filter) + { + $collection = $filter->getLayer()->getProductCollection(); + $collection->addPriceData($filter->getCustomerGroupId(), $filter->getWebsiteId()); + + // clone select from collection with filters + $select = clone $collection->getSelect(); + // reset columns, order and limitation conditions + $select->reset(Zend_Db_Select::COLUMNS); + $select->reset(Zend_Db_Select::ORDER); + $select->reset(Zend_Db_Select::LIMIT_COUNT); + $select->reset(Zend_Db_Select::LIMIT_OFFSET); + + return $select; + } + + /** + * Prepare response object and dispatch prepare price event + * + * Return response object + * + * @param Mage_Catalog_Model_Layer_Filter_Price $filter + * @param Varien_Db_Select $select + * @return Varien_Object + */ + protected function _dispatchPreparePriceEvent($filter, $select) + { + // prepare response object for event + $response = new Varien_Object(); + $response->setAdditionalCalculations(array()); + + // prepare event arguments + $eventArgs = array( + 'select' => $select, + 'table' => $this->_getIndexTableAlias(), + 'store_id' => $filter->getStoreId(), + 'response_object' => $response + ); + + /** + * @deprecated since 1.3.2.2 + */ + Mage::dispatchEvent('catalogindex_prepare_price_select', $eventArgs); + + /** + * @since 1.4 + */ + Mage::dispatchEvent('catalog_prepare_price_select', $eventArgs); + + return $response; + } + + /** + * Retrieve maximal price for attribute + * + * @param Mage_Catalog_Model_Layer_Filter_Price $filter + * @return float + */ + public function getMaxPrice($filter) + { + $select = $this->_getSelect($filter); + $connection = $this->_getReadAdapter(); + $response = $this->_dispatchPreparePriceEvent($filter, $select); + + $table = $this->_getIndexTableAlias(); + + $additional = join('', $response->getAdditionalCalculations()); + $maxPriceExpr = new Zend_Db_Expr("MAX({$table}.min_price {$additional})"); + + $select->columns(array($maxPriceExpr)); + + return $connection->fetchOne($select) * $filter->getCurrencyRate(); + } + + /** + * Retrieve array with products counts per price range + * + * @param Mage_Catalog_Model_Layer_Filter_Price $filter + * @param int $range + * @return array + */ + public function getCount($filter, $range) + { + $select = $this->_getSelect($filter); + $connection = $this->_getReadAdapter(); + $response = $this->_dispatchPreparePriceEvent($filter, $select); + + $table = $this->_getIndexTableAlias(); + + $additional = join('', $response->getAdditionalCalculations()); + $rate = $filter->getCurrencyRate(); + $countExpr = new Zend_Db_Expr("COUNT(*)"); + $rangeExpr = new Zend_Db_Expr("FLOOR((({$table}.min_price {$additional}) * {$rate}) / {$range}) + 1"); + + $select->columns(array( + 'range' => $rangeExpr, + 'count' => $countExpr + )); + $select->group('range'); + + return $connection->fetchPairs($select); + } + + /** + * Apply attribute filter to product collection + * + * @param Mage_Catalog_Model_Layer_Filter_Price $filter + * @param int $range + * @param int $index the range factor + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Layer_Filter_Attribute + */ + public function applyFilterToCollection($filter, $range, $index) + { + $collection = $filter->getLayer()->getProductCollection(); + $collection->addPriceData($filter->getCustomerGroupId(), $filter->getWebsiteId()); + $select = $collection->getSelect(); + $response = $this->_dispatchPreparePriceEvent($filter, $select); + + $table = $this->_getIndexTableAlias(); + $additional = join('', $response->getAdditionalCalculations()); + $rate = $filter->getCurrencyRate(); + $priceExpr = new Zend_Db_Expr("(({$table}.min_price {$additional}) * {$rate})"); + + $select + ->where($priceExpr . ' >= ?', ($range * ($index - 1))) + ->where($priceExpr . ' < ?', ($range * $index)); + + return $this; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product.php index 859be61e43..49c65b3097 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product.php @@ -110,13 +110,20 @@ public function getIdBySku($sku) */ protected function _beforeSave(Varien_Object $object) { + /** + * Try detect product id by sku if id is not declared + */ if (!$object->getId() && $object->getSku()) { $object->setId($this->getIdBySku($object->getSku())); } - $categoryIds = $object->getCategoryIds(); - if ($categoryIds) { - $categoryIds = Mage::getModel('catalog/category')->verifyIds($categoryIds); + /** + * Check if declared category ids in object data. + */ + if ($object->hasCategoryIds()) { + $categoryIds = Mage::getResourceSingleton('catalog/category')->verifyIds( + $object->getCategoryIds() + ); $object->setCategoryIds($categoryIds); } @@ -133,7 +140,8 @@ protected function _afterSave(Varien_Object $product) { $this->_saveWebsiteIds($product) ->_saveCategories($product) - ->refreshIndex($product); + //->refreshIndex($product) + ; parent::_afterSave($product); return $this; @@ -196,8 +204,13 @@ protected function _saveWebsiteIds($product) */ protected function _saveCategories(Varien_Object $object) { + /** + * If category ids data is not declared we haven't do manipulations + */ + if (!$object->hasCategoryIds()) { + return $this; + } $categoryIds = $object->getCategoryIds(); - $oldCategoryIds = $this->getCategoryIds($object); $object->setIsChangedCategories(false); @@ -337,7 +350,7 @@ public function refreshEnabledIndex($store=null, $product=null) $this->_getWriteAdapter()->delete($indexTable, 'store_id='.$storeId.$deleteCondition); $query = "INSERT INTO $indexTable SELECT - t_v_default.entity_id, {$storeId}, IFNULL(t_v.value, t_v_default.value) + t_v_default.entity_id, {$storeId}, IF(t_v.value_id>0, t_v.value, t_v_default.value) FROM {$visibilityTable} AS t_v_default INNER JOIN {$this->getTable('catalog/product_website')} AS w @@ -357,7 +370,7 @@ public function refreshEnabledIndex($store=null, $product=null) WHERE t_v_default.attribute_id='{$visibilityAttributeId}' AND t_v_default.store_id=0{$productsCondition} - AND (IFNULL(t_s.value, t_s_default.value)=".Mage_Catalog_Model_Product_Status::STATUS_ENABLED.")"; + AND (IF(t_s.value_id>0, t_s.value, t_s_default.value)=".Mage_Catalog_Model_Product_Status::STATUS_ENABLED.")"; $this->_getWriteAdapter()->query($query); } elseif (is_null($store)) { @@ -372,7 +385,7 @@ public function refreshEnabledIndex($store=null, $product=null) $this->_getWriteAdapter()->delete($indexTable, 'product_id='.$productId.' AND store_id='.$storeId); $query = "INSERT INTO $indexTable SELECT - {$productId}, {$storeId}, IFNULL(t_v.value, t_v_default.value) + {$productId}, {$storeId}, IF(t_v.value_id>0, t_v.value, t_v_default.value) FROM {$visibilityTable} AS t_v_default LEFT JOIN {$visibilityTable} AS `t_v` @@ -390,7 +403,7 @@ public function refreshEnabledIndex($store=null, $product=null) WHERE t_v_default.entity_id={$productId} AND t_v_default.attribute_id='{$visibilityAttributeId}' AND t_v_default.store_id=0 - AND (IFNULL(t_s.value, t_s_default.value)=".Mage_Catalog_Model_Product_Status::STATUS_ENABLED.")"; + AND (IF(t_s.value_id>0, t_s.value, t_s_default.value)=".Mage_Catalog_Model_Product_Status::STATUS_ENABLED.")"; $this->_getWriteAdapter()->query($query); } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Action.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Action.php new file mode 100644 index 0000000000..0658367138 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Action.php @@ -0,0 +1,86 @@ + + */ +class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Action extends Mage_Catalog_Model_Resource_Eav_Mysql4_Abstract +{ + /** + * Intialize connection + * + */ + protected function _construct() + { + $resource = Mage::getSingleton('core/resource'); + $this->setType('catalog_product') + ->setConnection( + $resource->getConnection('catalog_read'), + $resource->getConnection('catalog_write') + ); + } + + /** + * Update attribute values for entity list per store + * + * @param array $entityIds + * @param array $attrData + * @param int $storeId + * @return Mage_Catalog_Model_Product_Action + */ + public function updateAttributes($entityIds, $attrData, $storeId) + { + $object = new Varien_Object(); + $object->setIdFieldName('entity_id') + ->setStoreId($storeId); + + $this->_getWriteAdapter()->beginTransaction(); + try { + foreach ($attrData as $attrCode => $value) { + $attribute = $this->getAttribute($attrCode); + if (!$attribute->getAttributeId()) { + continue; + } + + foreach ($entityIds as $entityId) { + $object->setId($entityId); + $this->_saveAttributeValue($object, $attribute, $value); + } + } + $this->_getWriteAdapter()->commit(); + } catch (Exception $e) { + $this->_getWriteAdapter()->rollBack(); + throw $e; + } + + return $this; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Attribute/Backend/Tierprice.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Attribute/Backend/Tierprice.php index 1bfada9f94..f84576bfe6 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Attribute/Backend/Tierprice.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Attribute/Backend/Tierprice.php @@ -35,58 +35,151 @@ class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Backend_Tierprice extends Mage_Core_Model_Mysql4_Abstract { + /** + * Initialize connection and define main table + * + */ protected function _construct() { $this->_init('catalog/product_attribute_tier_price', 'value_id'); } + /** + * Load Tier Prices for product + * + * @param int $productId + * @param int $websiteId + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Backend_Tierprice + */ + public function loadPriceData($productId, $websiteId = null) + { + $adapter = $this->_getReadAdapter(); + $columns = array( + 'price_id' => $this->getIdFieldName(), + 'website_id' => 'website_id', + 'all_groups' => 'all_groups', + 'cust_group' => 'customer_group_id', + 'price_qty' => 'qty', + 'price' => 'value', + ); + $select = $adapter->select() + ->from($this->getMainTable(), $columns) + ->where('entity_id=?', $productId) + ->order('qty'); + if (!is_null($websiteId)) { + if ($websiteId == '0') { + $select->where('website_id=?', $websiteId); + } else { + $select->where('website_id IN(?)', array('0', $websiteId)); + } + } + + return $adapter->fetchAll($select); + } + + /** + * Delete Tier Prices for product + * + * @param int $productId + * @param int $websiteId + * @param int $priceId + * @return int The number of affected rows + */ + public function deletePriceData($productId, $websiteId = null, $priceId = null) + { + $adapter = $this->_getWriteAdapter(); + $conds = array( + $adapter->quoteInto('entity_id=?', $productId) + ); + if (!is_null($websiteId)) { + $where[] = $adapter->quoteInto('website_id=?', $websiteId); + } + if (!is_null($priceId)) { + $where[] = $adapter->quoteInto($this->getIdFieldName() . '=?', $priceId); + } + $where = join(' AND ', $conds); + + return $adapter->delete($this->getMainTable(), $where); + } + + /** + * Save tier price object + * + * @param Varien_Object $priceObject + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Backend_Tierprice + */ + public function savePriceData(Varien_Object $priceObject) + { + $adapter = $this->_getWriteAdapter(); + Mage::log($priceObject->debug()); + $data = $this->_prepareDataForTable($priceObject, $this->getMainTable()); + Mage::log($data); + if (!empty($data[$this->getIdFieldName()])) { + $where = $adapter->quoteInto($this->getIdFieldName() . '=?', $data[$this->getIdFieldName()]); + unset($data[$this->getIdFieldName()]); + $adapter->update($this->getMainTable(), $data, $where); + } else { + $adapter->insert($this->getMainTable(), $data); + } + return $this; + } + /** * Load product tier prices * * @param Mage_Catalog_Model_Product $product * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute + * @deprecated since 1.3.2.3 * @return array */ public function loadProductPrices($product, $attribute) { - $select = $this->_getReadAdapter()->select() - ->from($this->getMainTable(), array( - 'website_id', 'all_groups', 'cust_group' => 'customer_group_id', - 'price_qty' => 'qty', 'price' => 'value' - )) - ->where('entity_id=?', $product->getId()) - ->order('qty'); + $websiteId = null; if ($attribute->isScopeGlobal()) { - $select->where('website_id=?', 0); - } - else { - if ($storeId = $product->getStoreId()) { - $select->where('website_id IN (?)', array(0, Mage::app()->getStore($storeId)->getWebsiteId())); - } + $websiteId = 0; + } else if ($product->getStoreId()) { + $websiteId = Mage::app()->getStore($product->getStoreId())->getWebsiteId(); } - return $this->_getReadAdapter()->fetchAll($select); + + return $this->loadPriceData($product->getId(), $websiteId); } + /** + * Delete product tier price data from storage + * + * @param Mage_Catalog_Model_Product $product + * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute + * @deprecated since 1.3.2.3 + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Backend_Tierprice + */ public function deleteProductPrices($product, $attribute) { - $condition = array(); - + $websiteId = null; if (!$attribute->isScopeGlobal()) { - if ($storeId = $product->getStoreId()) { - $condition[] = $this->_getWriteAdapter()->quoteInto('website_id IN (?)', array(0, Mage::app()->getStore($storeId)->getWebsiteId())); + $storeId = $product->getProductId(); + if ($storeId) { + $websiteId = Mage::app()->getStore($storeId)->getWebsiteId(); } } - $condition[] = $this->_getWriteAdapter()->quoteInto('entity_id=?', $product->getId()); + $this->deletePriceData($product->getId(), $websiteId); - $this->_getWriteAdapter()->delete($this->getMainTable(), implode(' AND ', $condition)); return $this; } + /** + * Insert product Tier Price to storage + * + * @param Mage_Catalog_Model_Product $product + * @param array $data + * @deprecated since 1.3.2.3 + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Backend_Tierprice + */ public function insertProductPrice($product, $data) { - $data['entity_id'] = $product->getId(); - $this->_getWriteAdapter()->insert($this->getMainTable(), $data); - return $this; + $priceObject = new Varien_Object($data); + $priceObject->setEntityId($product->getId()); + + return $this->savePriceData($priceObject); } } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Attribute/Collection.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Attribute/Collection.php new file mode 100644 index 0000000000..28c1cc3231 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Attribute/Collection.php @@ -0,0 +1,124 @@ +getSelect()->from(array('main_table' => $this->getResource()->getMainTable())) + ->where('main_table.entity_type_id=?', Mage::getModel('eav/entity')->setType('catalog_product')->getTypeId()) + ->join( + array('additional_table' => $this->getTable('catalog/eav_attribute')), + 'additional_table.attribute_id=main_table.attribute_id' + ); + return $this; + } + + /** + * Specify attribute entity type filter + * + * @param int $typeId + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Collection + */ + public function setEntityTypeFilter($typeId) + { + return $this; + } + + /** + * Return array of fields to load attribute values + * + * @return array + */ + protected function _getLoadDataFields() + { + $fields = parent::_getLoadDataFields(); + $fields = array_merge($fields, array('additional_table.is_global')); + return $fields; + } + + /** + * Specify "is_visible_in_advanced_search" filter + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Collection + */ + public function addDisplayInAdvancedSearchFilter() + { + $this->getSelect()->where('additional_table.is_visible_in_advanced_search = ?', 1); + return $this; + } + + /** + * Specify "is_filterable" filter + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Collection + */ + public function addIsFilterableFilter() + { + $this->getSelect()->where('additional_table.is_filterable > ?', 0); + return $this; + } + + /** + * Add filterable in search filter + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Collection + */ + public function addIsFilterableInSearchFilter() + { + $this->getSelect()->where('additional_table.is_filterable_in_search > ?', 0); + return $this; + } + + /** + * Specify filter by "is_visible" field + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Collection + */ + public function addVisibleFilter() + { + $this->getSelect()->where('additional_table.is_visible=?', 1); + return $this; + } + + /** + * Specify "is_searchable" filter + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Collection + */ + public function addIsSearchableFilter() + { + $this->getSelect()->where('additional_table.is_searchable=1'); + return $this; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Collection.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Collection.php index 74341c80eb..62a29e50d3 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Collection.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Collection.php @@ -107,6 +107,9 @@ class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection * visibility array|int; * website_ids array|int; * store_table string; + * use_price_index bool; join price index table flag + * customer_group_id int; required for price; customer group limitation for price + * website_id int; required for price; website limitation for price * * @var array */ @@ -327,9 +330,9 @@ public function addAttributeToSelect($attribute, $joinType = false) */ protected function _beforeLoad() { - if ($this->_addFinalPrice) { - $this->_joinPriceRules(); - } +// if ($this->_addFinalPrice) { +// $this->_joinPriceRules(); +// } Mage::dispatchEvent('catalog_product_collection_load_before', array('collection'=>$this)); return parent::_beforeLoad(); @@ -346,12 +349,9 @@ protected function _afterLoad() if ($this->_addUrlRewrite) { $this->_addUrlRewrite($this->_urlRewriteCategory); } - if ($this->_addMinimalPrice) { - $this->_addMinimalPrice(); - } - if ($this->_addFinalPrice) { - $this->_addFinalPrice(); - } +// if ($this->_addFinalPrice) { +// $this->_addFinalPrice(); +// } $this->_prepareUrlDataObject(); @@ -755,12 +755,14 @@ public function addCountToCategories($categoryCollection) if ($isAnchor) { $anchorStmt = clone $select; + $anchorStmt->limit(); //reset limits $anchorStmt->where('count_table.category_id in (?)', $isAnchor); $productCounts += $this->getConnection()->fetchPairs($anchorStmt, array('category_id'=>'product_count')); $anchorStmt = null; } if ($isNotAnchor) { $notAnchorStmt = clone $select; + $notAnchorStmt->limit(); //reset limits $notAnchorStmt->where('count_table.category_id in (?)', $isNotAnchor); $notAnchorStmt->where('count_table.is_parent=1'); $productCounts += $this->getConnection()->fetchPairs($notAnchorStmt, array('category_id'=>'product_count')); @@ -838,7 +840,11 @@ public function joinUrlRewrite() public function addUrlRewrite($categoryId = '') { $this->_addUrlRewrite = true; - $this->_urlRewriteCategory = Mage::getStoreConfig('catalog/seo/product_use_categories') ? $categoryId : 0; + if (Mage::getStoreConfig(Mage_Catalog_Helper_Product::XML_PATH_PRODUCT_URL_USE_CATEGORY, $this->getStoreId())) { + $this->_urlRewriteCategory = $categoryId; + } else { + $this->_urlRewriteCategory = 0; + } return $this; } @@ -894,27 +900,36 @@ protected function _addUrlRewrite() } } + /** + * Add minimal price data to result + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection + */ public function addMinimalPrice() { - $this->_addMinimalPrice = true; - return $this; + return $this->addPriceData(); } + /** + * Add minimal price to product collection + * + * @deprecated sinse 1.3.2.2 + * @see Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection::addPriceData + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection + */ protected function _addMinimalPrice() { - Mage::getSingleton('catalogindex/price')->addMinimalPrices($this); return $this; } + /** + * Add price data for calculate final price + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection + */ public function addFinalPrice() { - $this->_addFinalPrice = true; - $this->addAttributeToSelect('price') - ->addAttributeToSelect('special_price') - ->addAttributeToSelect('special_from_date') - ->addAttributeToSelect('special_to_date'); - - return $this; + return $this->addPriceData(); } /** @@ -1001,6 +1016,36 @@ public function setAllIdsCache($value) return $this; } + /** + * Add Price Data to result + * + * @param int $customerGroupId + * @param int $websiteId + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection + */ + public function addPriceData($customerGroupId = null, $websiteId = null) + { + $this->_productLimitationFilters['use_price_index'] = true; + + if (!isset($this->_productLimitationFilters['customer_group_id']) && is_null($customerGroupId)) { + $customerGroupId = Mage::getSingleton('customer/session')->getCustomerGroupId(); + } + if (!isset($this->_productLimitationFilters['website_id']) && is_null($websiteId)) { + $websiteId = Mage::app()->getStore($this->getStoreId())->getWebsiteId(); + } + + if (!is_null($customerGroupId)) { + $this->_productLimitationFilters['customer_group_id'] = $customerGroupId; + } + if (!is_null($websiteId)) { + $this->_productLimitationFilters['website_id'] = $websiteId; + } + + $this->_applyProductLimitations(); + + return $this; + } + /** * Add attribute to filter * @@ -1039,7 +1084,8 @@ public function addAttributeToFilter($attribute, $condition=null, $joinType='inn $this->_allIdsCache = null; if (is_string($attribute) && $attribute == 'is_saleable') { - return $this->getSelect()->where($this->_getConditionSql('(IF(manage_stock, is_in_stock, 1))', $condition)); + $this->getSelect()->where($this->_getConditionSql('(IF(manage_stock, is_in_stock, 1))', $condition)); + return $this; } else { return parent::addAttributeToFilter($attribute, $condition, $joinType); @@ -1169,43 +1215,8 @@ public function addAttributeToSort($attribute, $dir='asc') $storeId = Mage::app()->getStore()->getId(); if ($attribute == 'price' && $storeId != 0) { - $websiteId = Mage::app()->getStore()->getWebsiteId(); - $customerGroup = Mage::getSingleton('customer/session')->getCustomerGroupId(); - - if ($this->isEnabledFlat()) { - $priceColumn = 'e.display_price_group_' . $customerGroup; - $this->getSelect()->order("{$priceColumn} {$dir}"); - } - else { - $priceAttributeId = $this->getAttribute('price')->getId(); - - $entityCondition = '_price_order_table.entity_id = e.entity_id'; - $storeCondition = $this->getConnection()->quoteInto( - '_price_order_table.website_id = ?', - $websiteId - ); - $groupCondition = $this->getConnection()->quoteInto( - '_price_order_table.customer_group_id = ?', - $customerGroup - ); - $attributeCondition = $this->getConnection()->quoteInto( - '_price_order_table.attribute_id = ?', - $priceAttributeId - ); - - $this->getSelect()->joinLeft( - array('_price_order_table'=>$this->getTable('catalogindex/price')), - "{$entityCondition} AND {$storeCondition} AND {$groupCondition} AND {$attributeCondition}", - array() - ); - $this->getSelect()->order('_price_order_table.value ' . $dir); - - /** - * Distinct we are using for remove duplicates of products which have - * several rows in price index (like grouped products) - */ - $this->getSelect()->distinct(true); - } + $this->addPriceData(); + $this->getSelect()->order("price_index.min_price {$dir}"); return $this; } @@ -1382,6 +1393,41 @@ protected function _productLimitationJoinStore() return $this; } + /** + * Join Product Price Table + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection + */ + protected function _productLimitationJoinPrice() + { + $filters = $this->_productLimitationFilters; + if (empty($filters['use_price_index'])) { + return $this; + } + + $connection = $this->getConnection(); + + $joinCond = $joinCond = join(' AND ', array( + 'price_index.entity_id = e.entity_id', + $connection->quoteInto('price_index.website_id = ?', $filters['website_id']), + $connection->quoteInto('price_index.customer_group_id = ?', $filters['customer_group_id']) + )); + + $fromPart = $this->getSelect()->getPart(Zend_Db_Select::FROM); + if (!isset($fromPart['price_index'])) { + $this->getSelect()->joinLeft( + array('price_index' => $this->getTable('catalog/product_index_price')), + $joinCond, + array('price', 'final_price', 'min_price', 'max_price', 'tier_price') + ); + } else { + $fromPart['price_index']['joinCondition'] = $joinCond; + $this->getSelect()->setPart(Zend_Db_Select::FROM, $fromPart); + } + + return $this; + } + /** * Apply limitation filters to collection * @@ -1396,6 +1442,7 @@ protected function _applyProductLimitations() { $this->_prepareProductLimitationFilters(); $this->_productLimitationJoinWebsite(); + $this->_productLimitationJoinPrice(); $filters = $this->_productLimitationFilters; if (!isset($filters['category_id']) && !isset($filters['visibility'])) { diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Compare/Item/Collection.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Compare/Item/Collection.php index de3f5e1b8d..91157d278a 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Compare/Item/Collection.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Compare/Item/Collection.php @@ -227,11 +227,19 @@ public function getComparableAttributes() $attributeIds = $this->_getAttributeIdsBySetIds($setIds); $select = $this->getConnection()->select() - ->from($this->getTable('eav/attribute')) - ->where('is_comparable=?', 1) - ->where('attribute_id IN(?)', $attributeIds); + ->from(array('main_table' => $this->getTable('eav/attribute'))) + ->join( + array('additional_table' => $this->getTable('catalog/eav_attribute')), + 'additional_table.attribute_id=main_table.attribute_id' + ) + ->joinLeft( + array('al' => $this->getTable('eav/attribute_label')), + 'al.attribute_id = main_table.attribute_id AND al.store_id = ' . (int) $this->getStoreId(), + array('store_label' => new Zend_Db_Expr('IFNULL(al.value, main_table.frontend_label)')) + ) + ->where('additional_table.is_comparable=?', 1) + ->where('main_table.attribute_id IN(?)', $attributeIds); $attributesData = $this->getConnection()->fetchAll($select); - if ($attributesData) { $entityType = 'catalog_product'; Mage::getSingleton('eav/config') diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Flat/Indexer.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Flat/Indexer.php index 075503c0fc..5915da7cf9 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Flat/Indexer.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Flat/Indexer.php @@ -87,6 +87,13 @@ class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer */ protected $_productTypes; + /** + * Exists flat tables cache + * + * @var array + */ + protected $_existsFlatTables = array(); + /** * Initialize connection * @@ -152,18 +159,22 @@ public function getAttributeCodes() $this->_attributeCodes = array(); $whereCond = array( - $this->_getReadAdapter()->quoteInto('backend_type=?', 'static'), - $this->_getReadAdapter()->quoteInto('used_in_product_listing=?', 1), - $this->_getReadAdapter()->quoteInto('used_for_sort_by=?', 1), - $this->_getReadAdapter()->quoteInto('attribute_code IN(?)', $this->_systemAttributes) + $this->_getReadAdapter()->quoteInto('main_table.backend_type=?', 'static'), + $this->_getReadAdapter()->quoteInto('additional_table.used_in_product_listing=?', 1), + $this->_getReadAdapter()->quoteInto('additional_table.used_for_sort_by=?', 1), + $this->_getReadAdapter()->quoteInto('main_table.attribute_code IN(?)', $this->_systemAttributes) ); if ($this->getFlatHelper()->isAddFilterableAttributes()) { - $whereCond[] = $this->_getReadAdapter()->quoteInto('is_filterable>?', 0); + $whereCond[] = $this->_getReadAdapter()->quoteInto('additional_table.is_filterable>?', 0); } $select = $this->_getReadAdapter()->select() - ->from($this->getTable('eav/attribute')) - ->where('entity_type_id=?', $this->getEntityTypeId()) + ->from(array('main_table' => $this->getTable('eav/attribute'))) + ->join( + array('additional_table' => $this->getTable('catalog/eav_attribute')), + 'additional_table.attribute_id=main_table.attribute_id' + ) + ->where('main_table.entity_type_id=?', $this->getEntityTypeId()) ->where(join(' OR ', $whereCond)); $attributesData = $this->_getReadAdapter()->fetchAll($select); Mage::getSingleton('eav/config') @@ -512,9 +523,7 @@ public function prepareFlatTable($store) $tableName = $this->getFlatTableName($store); $tableNameQuote = $this->_getWriteAdapter()->quoteIdentifier($tableName); - $tableExistsSql = $this->_getWriteAdapter() - ->quoteInto("SHOW TABLE STATUS LIKE ?", $tableName); - if (!$this->_getWriteAdapter()->fetchRow($tableExistsSql)) { + if (!$this->_isFlatTableExists($store)) { $sql = "CREATE TABLE {$tableNameQuote} (\n"; foreach ($columns as $field => $fieldProp) { $sql .= sprintf(" %s,\n", @@ -533,6 +542,8 @@ public function prepareFlatTable($store) } $sql .= "\n) ENGINE=InnoDB DEFAULT CHARSET=utf8"; $this->_getWriteAdapter()->query($sql); + + $this->_existsFlatTables[$store] = true; } else { $describe = $this->_getWriteAdapter()->describeTable($tableName); @@ -646,6 +657,10 @@ public function prepareFlatTable($store) */ public function updateStaticAttributes($store, $productIds = null) { + if (!$this->_isFlatTableExists($store)) { + return $this; + } + $website = Mage::app()->getStore($store)->getWebsite()->getId(); $status = $this->getAttribute('status'); /* @var $status Mage_Eav_Model_Entity_Attribute */ @@ -680,7 +695,7 @@ public function updateStaticAttributes($store, $productIds = null) ->where("t1.entity_type_id=?", $status->getEntityTypeId()) ->where("t1.attribute_id=?", $status->getId()) ->where("t1.store_id=?", 0) - ->where("IFNULL(`t2`.`value`, `t1`.`value`)=?", Mage_Catalog_Model_Product_Status::STATUS_ENABLED); + ->where("IF(`t2`.`value_id`>0, `t2`.`value`, `t1`.`value`)=?", Mage_Catalog_Model_Product_Status::STATUS_ENABLED); foreach ($this->getAttributes() as $attributeCode => $attribute) { /* @var $attribute Mage_Eav_Model_Entity_Attribute */ if ($attribute->getBackend()->getType() == 'static') { @@ -751,7 +766,16 @@ public function cleanNonWebsiteProducts($store, $productIds = null) */ public function updateAttribute($attribute, $store, $productIds = null) { + if (!$this->_isFlatTableExists($store)) { + return $this; + } + + $describe = $this->_getWriteAdapter()->describeTable($this->getFlatTableName($store)); + if ($attribute->getBackend()->getType() == 'static') { + if (!isset($describe[$attribute->getAttributeCode()])) { + return $this; + } $select = $this->_getWriteAdapter()->select() ->join( array('main_table' => $this->getTable('catalog/product')), @@ -768,6 +792,16 @@ public function updateAttribute($attribute, $store, $productIds = null) $this->_getWriteAdapter()->query($sql); } else { + $columns = $attribute->getFlatColumns(); + if (!$columns) { + return $this; + } + foreach (array_keys($columns) as $columnName) { + if (!isset($describe[$columnName])) { + return $this; + } + } + $select = $attribute->getFlatUpdateSelect($store); if ($select instanceof Varien_Db_Select) { if (!is_null($productIds)) { @@ -789,6 +823,10 @@ public function updateAttribute($attribute, $store, $productIds = null) */ public function updateEavAttributes($store, $productIds = null) { + if (!$this->_isFlatTableExists($store)) { + return $this; + } + foreach ($this->getAttributes() as $attribute) { /* @var $attribute Mage_Eav_Model_Entity_Attribute */ if ($attribute->getBackend()->getType() != 'static') { @@ -846,6 +884,10 @@ public function updateRelationProducts($store, $productIds = null) return $this; } + if (!$this->_isFlatTableExists($store)) { + return $this; + } + foreach ($this->getProductTypeInstances() as $typeInstance) { if (!$typeInstance->isComposite()) { continue; @@ -903,6 +945,10 @@ public function updateChildrenDataFromParent($store, $productIds = null) return $this; } + if (!$this->_isFlatTableExists($store)) { + return $this; + } + $select = $this->_getWriteAdapter()->select(); foreach (array_keys($this->getFlatColumns()) as $columnName) { if ($columnName == 'entity_id' || $columnName == 'child_id' || $columnName == 'is_child') { @@ -995,6 +1041,10 @@ public function cleanRelationProducts($store) */ public function removeProduct($productIds, $store) { + if (!$this->_isFlatTableExists($store)) { + return $this; + } + $cond = array( $this->_getWriteAdapter()->quoteInto('entity_id IN(?)', $productIds) ); @@ -1037,6 +1087,10 @@ public function removeProductChildren($productIds, $store) */ public function updateProduct($productIds, $store) { + if (!$this->_isFlatTableExists($store)) { + return $this; + } + $this->saveProduct($productIds, $store); Mage::dispatchEvent('catalog_product_flat_update_product', array( @@ -1057,6 +1111,10 @@ public function updateProduct($productIds, $store) */ public function saveProduct($productIds, $store) { + if (!$this->_isFlatTableExists($store)) { + return $this; + } + $this->updateStaticAttributes($store, $productIds); $this->updateEavAttributes($store, $productIds); @@ -1071,18 +1129,36 @@ public function saveProduct($productIds, $store) */ public function deleteFlatTable($store) { - $tableName = $this->getFlatTableName($store); - $tableNameQuote = $this->_getWriteAdapter()->quoteIdentifier($tableName); - $tableExistsSql = $this->_getWriteAdapter() - ->quoteInto("SHOW TABLE STATUS LIKE ?", $tableName); - if ($this->_getWriteAdapter()->query($tableExistsSql)) { - $sql = sprintf('DROP TABLE IF EXISTS %s', $tableNameQuote); + if ($this->_isFlatTableExists($store)) { + $tableName = $this->_getWriteAdapter()->quoteIdentifier($this->getFlatTableName($store)); + $sql = sprintf('DROP TABLE IF EXISTS %s', $tableName); $this->_getWriteAdapter()->query($sql); } return $this; } + /** + * Check is flat table for store exists + * + * @param int $store + * @return bool + */ + protected function _isFlatTableExists($store) + { + if (!isset($this->_existsFlatTables[$store])) { + $tableName = $this->getFlatTableName($store); + $tableExistsSql = $this->_getWriteAdapter() + ->quoteInto("SHOW TABLE STATUS LIKE ?", $tableName); + if ($this->_getWriteAdapter()->fetchRow($tableExistsSql)) { + $this->_existsFlatTables[$store] = true; + } else { + $this->_existsFlatTables[$store] = false; + } + } + return $this->_existsFlatTables[$store]; + } + /** * Retrieve previous key from array by key * diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Abstract.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Abstract.php new file mode 100644 index 0000000000..c9d7b68d6b --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Abstract.php @@ -0,0 +1,198 @@ + + */ +abstract class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Abstract + extends Mage_Index_Model_Mysql4_Abstract +{ + /** + * Retrieve catalog_product attribute instance by attribute code + * + * @param string $attributeCode + * @return Mage_Catalog_Model_Resource_Eav_Attribute + */ + protected function _getAttribute($attributeCode) + { + return Mage::getSingleton('eav/config')->getAttribute('catalog_product', $attributeCode); + } + + /** + * Add attribute join condition to select and return Zend_Db_Expr + * attribute value definition + * + * If $condition is not empty apply limitation for select + * + * @param Varien_Db_Select $select + * @param string $attrCode the attribute code + * @param string|Zend_Db_Expr $entity the entity field or expression for condition + * @param string|Zend_Db_Expr $store the store field or expression for condition + * @param Zend_Db_Expr $condition the limitation condition + * @param bool $required if required or has condition used INNER join, else - LEFT + * @return Zend_Db_Expr the attribute value expression + */ + protected function _addAttributeToSelect($select, $attrCode, $entity, $store, $condition = null, $required = false) + { + $attribute = $this->_getAttribute($attrCode); + $attributeId = $attribute->getAttributeId(); + $attributeTable = $attribute->getBackend()->getTable(); + $joinType = !is_null($condition) || $required ? 'join' : 'joinLeft'; + + if ($attribute->isScopeGlobal()) { + $alias = 'ta_' . $attrCode; + $select->$joinType( + array($alias => $attributeTable), + "{$alias}.entity_id = {$entity} AND {$alias}.attribute_id = {$attributeId}" + . " AND {$alias}.store_id = 0", + array() + ); + $expression = new Zend_Db_Expr("{$alias}.value"); + } else { + $dAlias = 'tad_' . $attrCode; + $sAlias = 'tas_' . $attrCode; + + $select->$joinType( + array($dAlias => $attributeTable), + "{$dAlias}.entity_id = {$entity} AND {$dAlias}.attribute_id = {$attributeId}" + . " AND {$dAlias}.store_id = 0", + array() + ); + $select->joinLeft( + array($sAlias => $attributeTable), + "{$sAlias}.entity_id = {$entity} AND {$sAlias}.attribute_id = {$attributeId}" + . " AND {$sAlias}.store_id = {$store}", + array() + ); + $expression = new Zend_Db_Expr("IF({$sAlias}.value_id > 0, {$sAlias}.value, {$dAlias}.value)"); + } + + if (!is_null($condition)) { + $select->where("{$expression}{$condition}"); + } + + return $expression; + } + + /** + * Add website data join to select + * + * If add default store join also limitation of only has default store website + * + * Joined table has aliases + * cw for website table, + * csg for store group table (joined by website default group) + * cs for store table (joined by website default store) + * + * @param Varien_Db_Select $select the select object + * @param bool $store add default store join + * @param string|Zend_Db_Expr $joinCondition the limitation for website_id + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Abstract + */ + protected function _addWebsiteJoinToSelect($select, $store = true, $joinCondition = null) + { + if (!is_null($joinCondition)) { + $joinCondition = 'cw.website_id = ' . $joinCondition; + } + + $select->join( + array('cw' => $this->getTable('core/website')), + $joinCondition, + array() + ); + + if ($store) { + $select->join( + array('csg' => $this->getTable('core/store_group')), + 'csg.group_id = cw.default_group_id', + array()) + ->join( + array('cs' => $this->getTable('core/store')), + 'cs.store_id = csg.default_store_id', + array()); + } + + return $this; + } + + /** + * Add join for catalog/product_website table + * + * Joined table has alias pw + * + * @param Varien_Db_Select $select the select object + * @param string|Zend_Db_Expr $website the limitation of website_id + * @param string|Zend_Db_Expr $product the limitation of product_id + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Abstract + */ + protected function _addProductWebsiteJoinToSelect($select, $website, $product) + { + $select->join( + array('pw' => $this->getTable('catalog/product_website')), + "pw.product_id = {$product} AND pw.website_id = {$website}", + array() + ); + + return $this; + } + + /** + * Retrieve product relations by children + * + * @param int|array $childIds + * @return array + */ + public function getRelationsByChild($childIds) + { + $write = $this->_getWriteAdapter(); + $select = $write->select() + ->from($this->getTable('catalog/product_relation'), 'parent_id') + ->where('child_id IN(?)', $childIds); + + return $write->fetchCol($select); + } + + /** + * Retrieve product relations by parents + * + * @param int|array $childIds + * @return array + */ + public function getRelationsByParent($parentIds) + { + $write = $this->_getWriteAdapter(); + $select = $write->select() + ->from($this->getTable('catalog/product_relation'), 'child_id') + ->where('parent_id IN(?)', $parentIds); + + return $write->fetchCol($select); + } +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Eav.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Eav.php new file mode 100644 index 0000000000..c9dbc7e481 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Eav.php @@ -0,0 +1,434 @@ +_init('catalog/product_index_eav', 'entity_id'); + } + + /** + * Reindex by entities + * + * @param int|array $processIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price + */ + protected function _reindexEntities($processIds) + { + $write = $this->_getWriteAdapter(); + + $this->cloneIndexTable(true); + + if (!is_array($processIds)) { + $processIds = array($processIds); + } + + $parentIds = $this->getRelationsByChild($processIds); + if ($parentIds) { + $processIds = array_unique(array_merge($processIds, $parentIds)); + } + $childIds = $this->getRelationsByParent($parentIds); + if ($childIds) { + $processIds = array_unique(array_merge($processIds, $childIds)); + } + + $this->_prepareSelectIndex($processIds); + $this->_prepareMultiselectIndex($processIds); + $this->_prepareRelationIndex($processIds); + $this->_removeNotVisibleEntityFromIndex(); + + $write->beginTransaction(); + try { + // remove old index + $where = $write->quoteInto('entity_id IN(?)', $processIds); + $write->delete($this->getMainTable(), $where); + + // insert new index + $this->insertFromTable($this->getIdxTable(), $this->getMainTable()); + + $this->commit(); + } catch (Exception $e) { + $this->rollBack(); + throw $e; + } + + return $this; + } + + /** + * Process product save. + * Method is responsible for index support + * when product was saved and assigned categories was changed. + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Eav + */ + public function catalogProductSave(Mage_Index_Model_Event $event) + { + $productId = $event->getEntityPk(); + $data = $event->getNewData(); + + /** + * Check if filterable attribute values were updated + */ + if (!isset($data['reindex_eav'])) { + return $this; + } + + return $this->_reindexEntities($productId); + } + + /** + * Process Product Delete + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Eav + */ + public function catalogProductDelete(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + if (empty($data['reindex_eav_parent_ids'])) { + return $this; + } + + return $this->_reindexEntities($data['reindex_eav_parent_ids']); + } + + /** + * Process Product Mass Update + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Eav + */ + public function catalogProductMassAction(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + if (empty($data['reindex_eav_product_ids'])) { + return $this; + } + + return $this->_reindexEntities($data['reindex_eav_product_ids']); + } + + /** + * Process Catalog Eav Attribute Save + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Eav + */ + public function catalogEavAttributeSave(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + if (empty($data['reindex_attribute'])) { + return $this; + } + + $attributeId = $event->getEntityPk(); + $write = $this->_getWriteAdapter(); + + if (empty($data['is_indexable'])) { + // remove attribute data from main index + $write->beginTransaction(); + try { + + $where = $write->quoteInto('attribute_id=?', $attributeId); + $write->delete($this->getMainTable(), $where); + $write->commit(); + } catch (Exception $e) { + $write->rollback(); + throw $e; + } + } else { + $this->cloneIndexTable(true); + + $this->_prepareSelectIndex(null, $attributeId); + $this->_prepareMultiselectIndex(null, $attributeId); + $this->_prepareRelationIndex(); + $this->_removeNotVisibleEntityFromIndex(); + + $this->beginTransaction(); + try { + // remove index by attribute + $where = $write->quoteInto('attribute_id=?', $attributeId); + $write->delete($this->getMainTable(), $where); + + // insert new index + $this->insertFromTable($this->getIdxTable(), $this->getMainTable()); + + $write->commit(); + } catch (Exception $e) { + $write->rollback(); + throw $e; + } + } + + return $this; + } + + /** + * Prepare temporary data index for select filtrable attribute + * + * @param array $entityIds the entity ids limitation + * @param int $attributeId the attribute id limitation + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Eav + */ + protected function _prepareSelectIndex($entityIds = null, $attributeId = null) + { + $write = $this->_getWriteAdapter(); + $idxTable = $this->getIdxTable(); + // prepare select attributes + if (is_null($attributeId)) { + $attrIds = $this->_getFilterableAttributeIds(false); + } else { + $attrIds = array($attributeId); + } + + $select = $write->select() + ->from( + array('pid' => $this->getValueTable('catalog/product', 'int')), + array('entity_id', 'attribute_id')) + ->join( + array('cs' => $this->getTable('core/store')), + '', + array('store_id')) + ->joinLeft( + array('pis' => $this->getValueTable('catalog/product', 'int')), + 'pis.entity_id = pid.entity_id AND pis.attribute_id = pid.attribute_id' + . ' AND pis.store_id=cs.store_id', + array('value' => new Zend_Db_Expr('IF(pis.value_id > 0, pis.value, pid.value)'))) + ->where('pid.store_id=?', 0) + ->where('cs.store_id!=?', 0) + ->where('pid.attribute_id IN(?)', $attrIds) + ->where('IF(pis.value_id > 0, pis.value, pid.value) IS NOT NULL'); + + $statusCond = $write->quoteInto('=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); + $this->_addAttributeToSelect($select, 'status', 'pid.entity_id', 'cs.store_id', $statusCond); + + if (!is_null($entityIds)) { + $select->where('pid.entity_id IN(?)', $entityIds); + } + + /** + * Add additional external limitation + */ + Mage::dispatchEvent('prepare_catalog_product_index_select', array( + 'select' => $select, + 'entity_field' => new Zend_Db_Expr('pid.entity_id'), + 'website_field' => new Zend_Db_Expr('cs.website_id'), + 'store_field' => new Zend_Db_Expr('cs.store_id') + )); + + $query = $select->insertFromSelect($idxTable); + $write->query($query); + + return $this; + } + + /** + * Prepare temporary data index for multiselect filtrable attribute + * + * @param array $entityIds the entity ids limitation + * @param int $attributeId the attribute id limitation + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Eav + */ + protected function _prepareMultiselectIndex($entityIds = null, $attributeId = null) + { + $write = $this->_getWriteAdapter(); + $idxTable = $this->getIdxTable(); + + // prepare multiselect attributes + if (is_null($attributeId)) { + $attrIds = $this->_getFilterableAttributeIds(true); + } else { + $attrIds = array($attributeId); + } + + $select = $write->select() + ->from( + array('pvd' => $this->getValueTable('catalog/product', 'varchar')), + array('entity_id', 'attribute_id')) + ->join( + array('cs' => $this->getTable('core/store')), + '', + array('store_id')) + ->joinLeft( + array('pvs' => $this->getValueTable('catalog/product', 'varchar')), + 'pvs.entity_id = pvd.entity_id AND pvs.attribute_id = pvd.attribute_id' + . ' AND pvs.store_id=cs.store_id', + array('value' => new Zend_Db_Expr('IF(pvs.value_id > 0, pvs.value, pvd.value)'))) + ->join( + array('eo' => $this->getTable('eav/attribute_option')), + 'FIND_IN_SET(eo.option_id, IF(pvs.value_id, pvs.value, pvd.value))', + array() + ) + ->where('pvd.store_id=?', 0) + ->where('cs.store_id!=?', 0) + ->where('pvd.attribute_id IN(?)', $attrIds); + + $statusCond = $write->quoteInto('=?', '=' . Mage_Catalog_Model_Product_Status::STATUS_ENABLED); + $this->_addAttributeToSelect($select, 'status', 'pvd.entity_id', 'cs.store_id', $statusCond); + + if (!is_null($entityIds)) { + $select->where('pvd.entity_id IN(?)', $entityIds); + } + + /** + * Add additional external limitation + */ + Mage::dispatchEvent('prepare_catalog_product_index_select', array( + 'select' => $select, + 'entity_field' => new Zend_Db_Expr('pvd.entity_id'), + 'website_field' => new Zend_Db_Expr('cs.website_id'), + 'store_field' => new Zend_Db_Expr('cs.store_id') + )); + + $query = $select->insertFromSelect($idxTable); + $write->query($query); + + return $this; + } + + /** + * Prepare temporary data index for product relations + * + * @param array $parentIds the parent entity ids limitation + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Eav + */ + protected function _prepareRelationIndex($parentIds = null) + { + $write = $this->_getWriteAdapter(); + $idxTable = $this->getIdxTable(); + + $select = $write->select() + ->from(array('l' => $this->getTable('catalog/product_relation')), 'parent_id') + ->join( + array('cs' => $this->getTable('core/store')), + '', + array()) + ->join( + array('i' => $idxTable), + 'l.child_id=i.entity_id AND cs.store_id = i.store_id', + array('attribute_id', 'store_id', 'value')); + if (!is_null($parentIds)) { + $select->where('l.parent_id IN(?)', $parentIds); + } + + /** + * Add additional external limitation + */ + Mage::dispatchEvent('prepare_catalog_product_index_select', array( + 'select' => $select, + 'entity_field' => new Zend_Db_Expr('l.parent_id'), + 'website_field' => new Zend_Db_Expr('cs.website_id'), + 'store_field' => new Zend_Db_Expr('cs.store_id') + )); + + $query = $select->insertIgnoreFromSelect($idxTable); + $write->query($query); + + return $this; + } + + /** + * Remove Not Visible products from temporary data index + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Eav + */ + protected function _removeNotVisibleEntityFromIndex() + { + $write = $this->_getWriteAdapter(); + $idxTable = $this->getIdxTable(); + + $select = $write->select() + ->from($idxTable, null); + + $condition = $write->quoteInto('=?',Mage_Catalog_Model_Product_Visibility::VISIBILITY_NOT_VISIBLE); + $this->_addAttributeToSelect($select, 'visibility', $idxTable.'.entity_id', $idxTable.'.store_id', $condition); + + $query = $select->deleteFromSelect($idxTable); + $write->query($query); + + return $this; + } + + /** + * Rebuild all index data + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Eav + */ + public function reindexAll() + { + $this->cloneIndexTable(true); + + $this->_prepareSelectIndex(); + $this->_prepareMultiselectIndex(); + $this->_prepareRelationIndex(); + $this->_removeNotVisibleEntityFromIndex(); + + $this->syncData(); + return $this; + } + + /** + * Retrieve filterable (used in LN) attribute ids + * + * @param bool $multiSelect + * @return array + */ + protected function _getFilterableAttributeIds($multiSelect) + { + $select = $this->_getReadAdapter()->select() + ->from(array('ca' => $this->getTable('catalog/eav_attribute')), 'attribute_id') + ->join( + array('ea' => $this->getTable('eav/attribute')), + 'ca.attribute_id = ea.attribute_id', + array()) + ->where('ca.is_filterable_in_search>0 OR ca.is_filterable>0'); + + if ($multiSelect == true) { + $select->where('ea.backend_type = ?', 'varchar') + ->where('ea.frontend_input = ?', 'multiselect'); + } else { + $select->where('ea.backend_type = ?', 'int') + ->where('ea.frontend_input = ?', 'select'); + } + + return $this->_getReadAdapter()->fetchCol($select); + } +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price.php new file mode 100644 index 0000000000..277a1f6e20 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price.php @@ -0,0 +1,452 @@ +_init('catalog/product_index_price', 'entity_id'); + } + + /** + * Retrieve parent ids and types by child id + * + * Return array with key product_id and value as product type id + * + * @param int $childId + * @return array + */ + public function getProductParentsByChild($childId) + { + $write = $this->_getWriteAdapter(); + $select = $write->select() + ->from(array('l' => $this->getTable('catalog/product_relation')), array('parent_id')) + ->join( + array('e' => $this->getTable('catalog/product')), + 'l.parent_id=e.entity_id', + array('e.type_id')) + ->where('l.child_id=?', $childId); + return $write->fetchPairs($select); + } + + /** + * Process produce delete + * + * If the deleted product was found in a composite product(s) update it + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price + */ + public function catalogProductDelete(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + if (empty($data['reindex_price_parent_ids'])) { + return $this; + } + + $this->cloneIndexTable(true); + + $processIds = array_keys($data['reindex_price_parent_ids']); + $parentIds = array(); + foreach ($data['reindex_price_parent_ids'] as $parentId => $parentType) { + $parentIds[$parentType][$parentId] = $parentId; + } + + $this->_copyRelationIndexData($processIds); + foreach ($parentIds as $parentType => $entityIds) { + $this->_getIndexer($parentType)->reindexEntity($entityIds); + } + + $this->_copyIndexDataToMainTable($parentIds); + + return $this; + } + + /** + * Copy data from temporary index table to main table by defined ids + * + * @param array $processIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price + */ + protected function _copyIndexDataToMainTable($processIds) + { + $write = $this->_getWriteAdapter(); + $write->beginTransaction(); + try { + // remove old index + $where = $write->quoteInto('entity_id IN(?)', $processIds); + $write->delete($this->getMainTable(), $where); + + // remove additional data from index + $where = $write->quoteInto('entity_id NOT IN(?)', $processIds); + $write->delete($this->getIdxTable(), $where); + + // insert new index + $this->insertFromTable($this->getIdxTable(), $this->getMainTable()); + + $this->commit(); + } catch (Exception $e) { + $this->rollBack(); + throw $e; + } + + return $this; + } + + /** + * Process product save. + * Method is responsible for index support + * when product was saved and changed attribute(s) has an effect on price. + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price + */ + public function catalogProductSave(Mage_Index_Model_Event $event) + { + $productId = $event->getEntityPk(); + $data = $event->getNewData(); + + /** + * Check if price attribute values were updated + */ + if (!isset($data['reindex_price'])) { + return $this; + } + + $this->cloneIndexTable(true); + + $indexer = $this->_getIndexer($data['product_type_id']); + $processIds = array($productId); + if ($indexer->getIsComposite()) { + $this->_copyRelationIndexData($productId); + $this->_prepareTierPriceIndex($productId); + $indexer->reindexEntity($productId); + } else { + $parentIds = $this->getProductParentsByChild($productId); + + if ($parentIds) { + $processIds = array_merge($processIds, array_keys($parentIds)); + $this->_copyRelationIndexData(array_keys($parentIds), $productId); + $this->_prepareTierPriceIndex($processIds); + $indexer->reindexEntity($productId); + + $parentByType = array(); + foreach ($parentIds as $parentId => $parentType) { + $parentByType[$parentType][$parentId] = $parentId; + } + + foreach ($parentByType as $parentType => $entityIds) { + $this->_getIndexer($parentType)->reindexEntity($entityIds); + } + } else { + $this->_prepareTierPriceIndex($productId); + $indexer->reindexEntity($productId); + } + } + + $this->_copyIndexDataToMainTable($processIds); + + return $this; + } + + /** + * Process product mass update action + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price + */ + public function catalogProductMassAction(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + if (empty($data['reindex_price_product_ids'])) { + return $this; + } + + $processIds = $data['reindex_price_product_ids']; + + $write = $this->_getWriteAdapter(); + $select = $write->select() + ->from($this->getTable('catalog/product'), 'COUNT(*)'); + $pCount = $write->fetchOne($select); + + // if affected more 30% of all products - run reindex all products + if ($pCount * 0.3 < count($processIds)) { + return $this->reindexAll(); + } + + // calculate relations + $select = $write->select() + ->from($this->getTable('catalog/product_relation'), 'COUNT(DISTINCT parent_id)') + ->where('child_id IN(?)', $processIds); + $aCount = $write->fetchOne($select); + $select = $write->select() + ->from($this->getTable('catalog/product_relation'), 'COUNT(DISTINCT child_id)') + ->where('parent_id IN(?)', $processIds); + $bCount = $write->fetchOne($select); + + // if affected with relations more 30% of all products - run reindex all products + if ($pCount * 0.3 < count($processIds) + $aCount + $bCount) { + return $this->reindexAll(); + } + + $this->cloneIndexTable(true); + + // retrieve products types + $select = $write->select() + ->from($this->getTable('catalog/product'), array('entity_id', 'type_id')) + ->where('entity_id IN(?)', $processIds); + $pairs = $write->fetchPairs($select); + $byType = array(); + foreach ($pairs as $productId => $productType) { + $byType[$productType][$productId] = $productId; + } + + $compositeIds = array(); + $notCompositeIds = array(); + + foreach ($byType as $productType => $entityIds) { + $indexer = $this->_getIndexer($productType); + if ($indexer->getIsComposite()) { + $compositeIds += $entityIds; + } else { + $notCompositeIds += $entityIds; + } + } + + if (!empty($notCompositeIds)) { + $select = $write->select() + ->from( + array('l' => $this->getTable('catalog/product_relation')), + 'parent_id') + ->join( + array('e' => $this->getTable('catalog/product')), + 'e.entity_id = l.parent_id', + array('type_id')) + ->where('l.child_id IN(?)', $notCompositeIds); + $pairs = $write->fetchPairs($select); + foreach ($pairs as $productId => $productType) { + if (!in_array($productId, $processIds)) { + $processIds[] = $productId; + $byType[$productType][$productId] = $productId; + $compositeIds[$productId] = $productId; + } + } + } + + if (!empty($compositeIds)) { + $this->_copyRelationIndexData($compositeIds, $notCompositeIds); + } + + $indexers = $this->getTypeIndexers(); + foreach ($indexers as $indexer) { + if (!empty($byType[$indexer->getTypeId()])) { + $indexer->reindexEntity($byType[$indexer->getTypeId()]); + } + } + + $this->_copyIndexDataToMainTable($processIds); + + return $this; + } + + /** + * Retrieve Price indexer by Product Type + * + * @param string $productTypeId + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + protected function _getIndexer($productTypeId) + { + $types = $this->getTypeIndexers(); + if (!isset($types[$productTypeId])) { + Mage::throwException(Mage::helper('catalog')->__('Unsupported product type "%s"', $productTypeId)); + } + return $types[$productTypeId]; + } + + /** + * Retrieve price indexers per product type + * + * @return array + */ + public function getTypeIndexers() + { + if (is_null($this->_indexers)) { + $this->_indexers = array(); + $types = Mage::getSingleton('catalog/product_type')->getTypesByPriority(); + foreach ($types as $typeId => $typeInfo) { + if (isset($typeInfo['price_indexer'])) { + $modelName = $typeInfo['price_indexer']; + } else { + $modelName = $this->_defaultPriceIndexer; + } + $isComposite = !empty($typeInfo['composite']); + $indexer = Mage::getResourceModel($modelName) + ->setTypeId($typeId) + ->setIsComposite($isComposite); + + $this->_indexers[$typeId] = $indexer; + } + } + + return $this->_indexers; + } + + /** + * Rebuild all index data + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Eav + */ + public function reindexAll() + { + $this->cloneIndexTable(true); + $this->_prepareTierPriceIndex(); + + $indexers = $this->getTypeIndexers(); + foreach ($indexers as $indexer) { + /* @var $indexer Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface */ + $indexer->reindexAll(); + } + + $this->syncData(); + return $this; + } + + /** + * Retrieve table name for product tier price index + * + * @return string + */ + protected function _getTierPriceIndexTable() + { + return $this->getIdxTable($this->getValueTable('catalog/product', 'tier_price')); + } + + /** + * Prepare tier price index table + * + * @param int|array $entityIds the entity ids limitation + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price + */ + protected function _prepareTierPriceIndex($entityIds = null) + { + $write = $this->_getWriteAdapter(); + $table = $this->_getTierPriceIndexTable(); + + $query = sprintf('DROP TABLE IF EXISTS %s', $write->quoteIdentifier($table)); + $write->query($query); + + $query = sprintf('CREATE TABLE %s (' + . ' `entity_id` INT(10) UNSIGNED NOT NULL,' + . ' `customer_group_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `website_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `min_price` DECIMAL(12,4) DEFAULT NULL,' + . ' PRIMARY KEY (`entity_id`,`customer_group_id`,`website_id`)' + . ') ENGINE=INNODB DEFAULT CHARSET=utf8', + $write->quoteIdentifier($table)); + $write->query($query); + + $select = $write->select() + ->from( + array('tp' => $this->getValueTable('catalog/product', 'tier_price')), + array('entity_id')) + ->join( + array('cg' => $this->getTable('customer/customer_group')), + 'tp.all_groups = 1 OR (tp.all_groups = 0 AND tp.customer_group_id = cg.customer_group_id)', + array('customer_group_id')) + ->join( + array('cw' => $this->getTable('core/website')), + 'tp.website_id = 0 OR tp.website_id = cw.website_id', + array('website_id')) + ->where('cw.website_id != 0') + ->columns(new Zend_Db_Expr('MIN(tp.value)')) + ->group(array('tp.entity_id', 'cg.customer_group_id', 'cw.website_id')); + + if (!empty($entityIds)) { + $select->where('tp.entity_id IN(?)', $entityIds); + } + + $query = $select->insertFromSelect($table); + $write->query($query); + + return $this; + } + + /** + * Copy relations product index from primary index to temporary index table by parent entity + * + * @param array|int $parentIds + * @package array|int $excludeIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price + */ + protected function _copyRelationIndexData($parentIds, $excludeIds = null) + { + $write = $this->_getWriteAdapter(); + $select = $write->select() + ->from($this->getTable('catalog/product_relation'), array('child_id')) + ->where('parent_id IN(?)', $parentIds); + if (!is_null($excludeIds)) { + $select->where('child_id NOT IN(?)', $excludeIds); + } + + $children = $write->fetchCol($select); + + if ($children) { + $select = $write->select() + ->from($this->getMainTable()) + ->where('entity_id IN(?)', $children); + $query = $select->insertFromSelect($this->getIdxTable()); + $write->query($query); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Configurable.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Configurable.php new file mode 100644 index 0000000000..c134cd7b07 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Configurable.php @@ -0,0 +1,226 @@ + + */ +class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Configurable + extends Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default +{ + /** + * Reindex temporary (price result data) for all products + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + public function reindexAll() + { + $this->_prepareFinalPriceData(); + $this->_applyCustomOption(); + $this->_applyConfigurableOption(); + $this->_movePriceDataToIndexTable(); + + return $this; + } + + /** + * Reindex temporary (price result data) for defined product(s) + * + * @param int|array $entityIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + public function reindexEntity($entityIds) + { + $this->_prepareFinalPriceData($entityIds); + $this->_applyCustomOption(); + $this->_applyConfigurableOption(); + $this->_movePriceDataToIndexTable(); + + return $this; + } + + /** + * Retrieve table name for custom option temporary aggregation data + * + * @return string + */ + protected function _getConfigurableOptionAggregateTable() + { + return $this->getIdxTable() . '_cfg_opt_aggregate'; + } + + /** + * Retrieve table name for custom option prices data + * + * @return string + */ + protected function _getConfigurableOptionPriceTable() + { + return $this->getIdxTable() . '_cfg_option'; + } + + /** + * Prepare table structure for custom option temporary aggregation data + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default + */ + protected function _prepareConfigurableOptionAggregateTable() + { + $write = $this->_getWriteAdapter(); + $table = $this->_getConfigurableOptionAggregateTable(); + + $query = sprintf('DROP TABLE IF EXISTS %s', $write->quoteIdentifier($table)); + $write->query($query); + + $query = sprintf('CREATE TABLE %s (' + . ' `parent_id` INT(10) UNSIGNED NOT NULL,' + . ' `child_id` INT(10) UNSIGNED NOT NULL,' + . ' `customer_group_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `website_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `price` DECIMAL(12,4) DEFAULT NULL,' + . ' `tier_price` DECIMAL(12,4) DEFAULT NULL,' + . ' PRIMARY KEY (`parent_id`, `child_id`, `customer_group_id`, `website_id`)' + . ') ENGINE=MYISAM DEFAULT CHARSET=utf8', + $write->quoteIdentifier($table)); + $write->query($query); + + return $this; + } + + /** + * Prepare table structure for custom option prices data + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default + */ + protected function _prepareConfigurableOptionPriceTable() + { + $write = $this->_getWriteAdapter(); + $table = $this->_getConfigurableOptionPriceTable(); + + $query = sprintf('DROP TABLE IF EXISTS %s', $write->quoteIdentifier($table)); + $write->query($query); + + $query = sprintf('CREATE TABLE %s (' + . ' `entity_id` INT(10) UNSIGNED NOT NULL,' + . ' `customer_group_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `website_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `min_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `max_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `tier_price` DECIMAL(12,4) DEFAULT NULL,' + . ' PRIMARY KEY (`entity_id`,`customer_group_id`,`website_id`)' + . ') ENGINE=MYISAM DEFAULT CHARSET=utf8', + $write->quoteIdentifier($table)); + $write->query($query); + + return $this; + } + + /** + * Calculate minimal and maximal prices for configurable product options + * and apply it to final price + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Configurable + */ + protected function _applyConfigurableOption() + { + $write = $this->_getWriteAdapter(); + $coaTable = $this->_getConfigurableOptionAggregateTable(); + $copTable = $this->_getConfigurableOptionPriceTable(); + + $this->_prepareConfigurableOptionAggregateTable(); + $this->_prepareConfigurableOptionPriceTable(); + + $select = $write->select() + ->from(array('i' => $this->_getDefaultFinalPriceTable()), null) + ->join( + array('l' => $this->getTable('catalog/product_super_link')), + 'l.parent_id = i.entity_id', + array('parent_id', 'product_id')) + ->columns(array('customer_group_id', 'website_id'), 'i') + ->join( + array('a' => $this->getTable('catalog/product_super_attribute')), + 'l.parent_id = a.product_id', + array()) + ->join( + array('cp' => $this->getValueTable('catalog/product', 'int')), + 'l.product_id = cp.entity_id AND cp.attribute_id = a.attribute_id AND cp.store_id = 0', + array()) + ->joinLeft( + array('apd' => $this->getTable('catalog/product_super_attribute_pricing')), + 'a.product_super_attribute_id = apd.product_super_attribute_id' + . ' AND apd.website_id = 0 AND cp.value = apd.value_index', + array()) + ->joinLeft( + array('apw' => $this->getTable('catalog/product_super_attribute_pricing')), + 'a.product_super_attribute_id = apw.product_super_attribute_id' + . ' AND apw.website_id = i.website_id AND cp.value = apw.value_index', + array()) + ->columns(new Zend_Db_Expr("SUM(IF((@price:=IF(apw.value_id, apw.pricing_value, apd.pricing_value))" + . " IS NULL, 0, IF(IF(apw.value_id, apw.is_percent, apd.is_percent) = 1, " + . "ROUND(i.price * (@price / 100), 4), @price)))")) + ->columns(new Zend_Db_Expr("IF(i.tier_price IS NOT NULL, SUM(IF((@tier_price:=" + . "IF(apw.value_id, apw.pricing_value, apd.pricing_value)) IS NULL, 0, IF(" + . "IF(apw.value_id, apw.is_percent, apd.is_percent) = 1, " + . "ROUND(i.price * (@tier_price / 100), 4), @tier_price))), NULL)")) + ->group(array('l.parent_id', 'i.customer_group_id', 'i.website_id', 'l.product_id')); + + $query = $select->insertFromSelect($coaTable); + $write->query($query); + + $select = $write->select() + ->from( + array($coaTable), + array('parent_id', 'customer_group_id', 'website_id', 'MIN(price)', 'MAX(price)', 'MIN(tier_price)')) + ->group('parent_id', 'customer_group_id', 'website_id'); + + $query = $select->insertFromSelect($copTable); + $write->query($query); + + $table = array('i' => $this->_getDefaultFinalPriceTable()); + $select = $write->select() + ->join( + array('io' => $copTable), + 'i.entity_id = io.entity_id AND i.customer_group_id = io.customer_group_id' + .' AND i.website_id = io.website_id', + array()); + $select->columns(array( + 'min_price' => new Zend_Db_Expr('i.min_price + io.min_price'), + 'max_price' => new Zend_Db_Expr('i.max_price + io.max_price'), + 'tier_price' => new Zend_Db_Expr('IF(i.tier_price IS NOT NULL, i.tier_price + io.tier_price, NULL)'), + )); + $query = $select->crossUpdateFromSelect($table); + $write->query($query); + + $write->truncate($coaTable); + $write->truncate($copTable); + + return $this; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Default.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Default.php new file mode 100644 index 0000000000..6ccf50fc13 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Default.php @@ -0,0 +1,611 @@ + + */ +class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default + extends Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Abstract + implements Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface +{ + /** + * Product type code + * + * @var string + */ + protected $_typeId; + + /** + * Product Type is composite flag + * + * @var bool + */ + protected $_isComposite = false; + + /** + * Define main price index table + * + */ + protected function _construct() + { + $this->_init('catalog/product_index_price', 'entity_id'); + } + + /** + * Set Product Type code + * + * @param string $typeCode + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default + */ + public function setTypeId($typeCode) + { + $this->_typeId = $typeCode; + return $this; + } + + /** + * Retrieve Product Type Code + * + * @return string + */ + public function getTypeId() + { + if (is_null($this->_typeId)) { + Mage::throwException(Mage::helper('catalog')->__('Not defined Product Type for Indexer')); + } + return $this->_typeId; + } + + /** + * Set Product Type Composite flag + * + * @param bool $flag + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default + */ + public function setIsComposite($flag) + { + $this->_isComposite = (bool)$flag; + return $this; + } + + /** + * Check product type is composite + * + * @return bool + */ + public function getIsComposite() + { + return $this->_isComposite; + } + + /** + * Reindex temporary (price result data) for all products + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + public function reindexAll() + { + $this->_prepareFinalPriceData(); + $this->_applyCustomOption(); + $this->_movePriceDataToIndexTable(); + return $this; + } + + /** + * Reindex temporary (price result data) for defined product(s) + * + * @param int|array $entityIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + public function reindexEntity($entityIds) + { + $this->_prepareFinalPriceData($entityIds); + $this->_applyCustomOption(); + $this->_movePriceDataToIndexTable(); + + return $this; + } + + /** + * Retrieve website current dates table name + * + * @return string + */ + protected function _getWebsiteDateTable() + { + return $this->getIdxTable($this->getValueTable('core/website', 'date')); + } + + /** + * Retrieve final price temporary index table name + * + * @see _prepareDefaultFinalPriceTable() + * @return string + */ + protected function _getDefaultFinalPriceTable() + { + return $this->getIdxTable($this->getMainTable() . '_final'); + } + + /** + * Prepare website current dates table + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default + */ + protected function _prepareWebsiteDateTable() + { + $write = $this->_getWriteAdapter(); + $table = $this->_getWebsiteDateTable(); + + $query = sprintf('DROP TABLE IF EXISTS %s', $write->quoteIdentifier($table)); + $write->query($query); + + $query = sprintf('CREATE TABLE %s (' + . ' `website_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `date` DATE DEFAULT NULL,' + . ' PRIMARY KEY (`website_id`),' + . ' KEY `IDX_DATE` (`date`)' + . ') ENGINE=INNODB DEFAULT CHARSET=utf8', + $write->quoteIdentifier($table)); + $write->query($query); + + $data = array(); + /* @var $coreDate Mage_Core_Model_Date */ + $websites = Mage::app()->getWebsites(false); + foreach ($websites as $website) { + /* @var $website Mage_Core_Model_Website */ + $store = $website->getDefaultStore(); + if ($store) { + $timestamp = Mage::app()->getLocale()->storeTimeStamp($store); + $data[] = array( + 'website_id' => $website->getId(), + 'date' => $this->formatDate($timestamp, false) + ); + } + } + + if ($data) { + $write->insertMultiple($table, $data); + } + + return $this; + } + + /** + * Prepare final price temporary index table + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default + */ + protected function _prepareDefaultFinalPriceTable() + { + $write = $this->_getWriteAdapter(); + $table = $this->_getDefaultFinalPriceTable(); + + $query = sprintf('DROP TABLE IF EXISTS %s', $write->quoteIdentifier($table)); + $write->query($query); + + $query = sprintf('CREATE TABLE %s (' + . ' `entity_id` INT(10) UNSIGNED NOT NULL,' + . ' `customer_group_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `website_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `tax_class_id` SMALLINT(5) UNSIGNED DEFAULT \'0\',' + . ' `orig_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `price` DECIMAL(12,4) DEFAULT NULL,' + . ' `min_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `max_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `tier_price` DECIMAL(12,4) DEFAULT NULL,' + . ' PRIMARY KEY (`entity_id`,`customer_group_id`,`website_id`)' + . ') ENGINE=MYISAM DEFAULT CHARSET=utf8', + $write->quoteIdentifier($table)); + $write->query($query); + + return $this; + } + + /** + * Prepare products default final price in temporary index table + * + * @param int|array $entityIds the entity ids limitation + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default + */ + protected function _prepareFinalPriceData($entityIds = null) + { + $this->_prepareWebsiteDateTable(); + $this->_prepareDefaultFinalPriceTable(); + + $write = $this->_getWriteAdapter(); + $select = $write->select() + ->from(array('e' => $this->getTable('catalog/product')), array('entity_id')) + ->join( + array('cg' => $this->getTable('customer/customer_group')), + '', + array('customer_group_id')) + ->join( + array('cw' => $this->getTable('core/website')), + '', + array('website_id')) + ->join( + array('cwd' => $this->_getWebsiteDateTable()), + 'cw.website_id = cwd.website_id', + array()) + ->join( + array('csg' => $this->getTable('core/store_group')), + 'csg.website_id = cw.website_id AND cw.default_group_id = csg.group_id', + array()) + ->join( + array('cs' => $this->getTable('core/store')), + 'csg.default_store_id = cs.store_id AND cs.store_id != 0', + array()) + ->join( + array('pw' => $this->getTable('catalog/product_website')), + 'pw.product_id = e.entity_id AND pw.website_id = cw.website_id', + array()) + ->joinLeft( + array('tp' => $this->_getTierPriceIndexTable()), + 'tp.entity_id = e.entity_id AND tp.website_id = cw.website_id' + . ' AND tp.customer_group_id = cg.customer_group_id', + array()) + ->where('e.type_id=?', $this->getTypeId()); + + // add enable products limitation + $statusCond = $write->quoteInto('=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); + $this->_addAttributeToSelect($select, 'status', 'e.entity_id', 'cs.store_id', $statusCond, true); + + $taxClassId = $this->_addAttributeToSelect($select, 'tax_class_id', 'e.entity_id', 'cs.store_id'); + $select->columns(array('tax_class_id' => $taxClassId)); + + $price = $this->_addAttributeToSelect($select, 'price', 'e.entity_id', 'cs.store_id'); + $specialPrice = $this->_addAttributeToSelect($select, 'special_price', 'e.entity_id', 'cs.store_id'); + $specialFrom = $this->_addAttributeToSelect($select, 'special_from_date', 'e.entity_id', 'cs.store_id'); + $specialTo = $this->_addAttributeToSelect($select, 'special_to_date', 'e.entity_id', 'cs.store_id'); + $curentDate = new Zend_Db_Expr('cwd.date'); + + $finalPrice = new Zend_Db_Expr("IF(IF({$specialFrom} IS NULL, 1, " + . "IF({$specialFrom} <= {$curentDate}, 1, 0)) > 0 AND IF({$specialTo} IS NULL, 1, " + . "IF({$specialTo} >= {$curentDate}, 1, 0)) > 0 AND {$specialPrice} < {$price}, " + . "{$specialPrice}, {$price})"); + $select->columns(array( + 'orig_price' => $price, + 'price' => $finalPrice, + 'min_price' => $finalPrice, + 'max_price' => $finalPrice, + 'tier_price' => new Zend_Db_Expr('tp.min_price') + )); + + if (!is_null($entityIds)) { + $select->where('e.entity_id IN(?)', $entityIds); + } + + /** + * Add additional external limitation + */ + Mage::dispatchEvent('prepare_catalog_product_index_select', array( + 'select' => $select, + 'entity_field' => new Zend_Db_Expr('e.entity_id'), + 'website_field' => new Zend_Db_Expr('cw.website_id'), + 'store_field' => new Zend_Db_Expr('cs.store_id') + )); + + $query = $select->insertFromSelect($this->_getDefaultFinalPriceTable()); + $write->query($query); + + /** + * Add possibility modify prices from external events + */ + $select = $write->select() + ->join(array('wd' => $this->_getWebsiteDateTable()), + 'i.website_id = wd.website_id', + array()); + Mage::dispatchEvent('prepare_catalog_product_price_index_table', array( + 'index_table' => array('i' => $this->_getDefaultFinalPriceTable()), + 'select' => $select, + 'entity_id' => 'i.entity_id', + 'customer_group_id' => 'i.customer_group_id', + 'website_id' => 'i.website_id', + 'website_date' => 'wd.date', + 'update_fields' => array('price', 'min_price', 'max_price') + )); + + return $this; + } + + /** + * Retrieve table name for custom option temporary aggregation data + * + * @return string + */ + protected function _getCustomOptionAggregateTable() + { + return $this->getIdxTable() . '_option_aggregate'; + } + + /** + * Retrieve table name for custom option prices data + * + * @return string + */ + protected function _getCustomOptionPriceTable() + { + return $this->getIdxTable() . '_option'; + } + + /** + * Prepare table structure for custom option temporary aggregation data + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default + */ + protected function _prepareCustomOptionAggregateTable() + { + $write = $this->_getWriteAdapter(); + $table = $this->_getCustomOptionAggregateTable(); + + $query = sprintf('DROP TABLE IF EXISTS %s', $write->quoteIdentifier($table)); + $write->query($query); + + $query = sprintf('CREATE TABLE %s (' + . ' `entity_id` INT(10) UNSIGNED NOT NULL,' + . ' `customer_group_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `website_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `option_id` INT(10) UNSIGNED DEFAULT \'0\',' + . ' `min_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `max_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `tier_price` DECIMAL(12,4) DEFAULT NULL,' + . ' PRIMARY KEY (`entity_id`,`customer_group_id`,`website_id`, `option_id`)' + . ') ENGINE=MYISAM DEFAULT CHARSET=utf8', + $write->quoteIdentifier($table)); + $write->query($query); + + return $this; + } + + /** + * Prepare table structure for custom option prices data + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default + */ + protected function _prepareCustomOptionPriceTable() + { + $write = $this->_getWriteAdapter(); + $table = $this->_getCustomOptionPriceTable(); + + $query = sprintf('DROP TABLE IF EXISTS %s', $write->quoteIdentifier($table)); + $write->query($query); + + $query = sprintf('CREATE TABLE %s (' + . ' `entity_id` INT(10) UNSIGNED NOT NULL,' + . ' `customer_group_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `website_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `min_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `max_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `tier_price` DECIMAL(12,4) DEFAULT NULL,' + . ' PRIMARY KEY (`entity_id`,`customer_group_id`,`website_id`)' + . ') ENGINE=MYISAM DEFAULT CHARSET=utf8', + $write->quoteIdentifier($table)); + $write->query($query); + + return $this; + } + + /** + * Apply custom option minimal and maximal price to temporary final price index table + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default + */ + protected function _applyCustomOption() + { + $write = $this->_getWriteAdapter(); + $coaTable = $this->_getCustomOptionAggregateTable(); + $copTable = $this->_getCustomOptionPriceTable(); + + $this->_prepareCustomOptionAggregateTable(); + $this->_prepareCustomOptionPriceTable(); + + $select = $write->select() + ->from( + array('i' => $this->_getDefaultFinalPriceTable()), + array('entity_id', 'customer_group_id', 'website_id')) + ->join( + array('cw' => $this->getTable('core/website')), + 'cw.website_id = i.website_id', + array()) + ->join( + array('csg' => $this->getTable('core/store_group')), + 'csg.group_id = cw.default_group_id', + array()) + ->join( + array('cs' => $this->getTable('core/store')), + 'cs.store_id = csg.default_store_id', + array()) + ->join( + array('o' => $this->getTable('catalog/product_option')), + 'o.product_id = i.entity_id', + array('option_id')) + ->join( + array('ot' => $this->getTable('catalog/product_option_type_value')), + 'ot.option_id = o.option_id', + array()) + ->join( + array('otpd' => $this->getTable('catalog/product_option_type_price')), + 'otpd.option_type_id = ot.option_type_id AND otpd.store_id = 0', + array()) + ->joinLeft( + array('otps' => $this->getTable('catalog/product_option_type_price')), + 'otps.option_type_id = otpd.option_type_id AND otpd.store_id = cs.store_id', + array()) + ->group(array('i.entity_id', 'i.customer_group_id', 'i.website_id', 'o.option_id')); + + $minPrice = new Zend_Db_Expr("IF(o.is_require, MIN(IF(IF(otps.option_type_price_id>0, otps.price_type, " + . "otpd.price_type)='fixed', IF(otps.option_type_price_id>0, otps.price, otpd.price), " + . "ROUND(i.price * (IF(otps.option_type_price_id>0, otps.price, otpd.price) / 100), 4))), 0)"); + $tierPrice = new Zend_Db_Expr("IF(i.tier_price IS NOT NULL, IF(o.is_require, " + . "MIN(IF(IF(otps.option_type_price_id>0, otps.price_type, otpd.price_type)='fixed', " + . "IF(otps.option_type_price_id>0, otps.price, otpd.price), " + . "ROUND(i.tier_price * (IF(otps.option_type_price_id>0, otps.price, otpd.price) / 100), 4))), 0), NULL)"); + $maxPrice = new Zend_Db_Expr("IF((o.type='radio' OR o.type='drop_down'), " + . "MAX(IF(IF(otps.option_type_price_id>0, otps.price_type, otpd.price_type)='fixed', " + . "IF(otps.option_type_price_id>0, otps.price, otpd.price), " + . "ROUND(i.price * (IF(otps.option_type_price_id>0, otps.price, otpd.price) / 100), 4))), " + . "SUM(IF(IF(otps.option_type_price_id>0, otps.price_type, otpd.price_type)='fixed', " + . "IF(otps.option_type_price_id>0, otps.price, otpd.price), " + . "ROUND(i.price * (IF(otps.option_type_price_id>0, otps.price, otpd.price) / 100), 4))))"); + + $select->columns(array( + 'min_price' => $minPrice, + 'max_price' => $maxPrice, + 'tier_price' => $tierPrice + )); + + $query = $select->insertFromSelect($coaTable); + $write->query($query); + + $select = $write->select() + ->from( + array('i' => $this->_getDefaultFinalPriceTable()), + array('entity_id', 'customer_group_id', 'website_id')) + ->join( + array('cw' => $this->getTable('core/website')), + 'cw.website_id = i.website_id', + array()) + ->join( + array('csg' => $this->getTable('core/store_group')), + 'csg.group_id = cw.default_group_id', + array()) + ->join( + array('cs' => $this->getTable('core/store')), + 'cs.store_id = csg.default_store_id', + array()) + ->join( + array('o' => $this->getTable('catalog/product_option')), + 'o.product_id = i.entity_id', + array('option_id')) + ->join( + array('opd' => $this->getTable('catalog/product_option_price')), + 'opd.option_id = o.option_id AND opd.store_id = 0', + array()) + ->joinLeft( + array('ops' => $this->getTable('catalog/product_option_price')), + 'ops.option_id = opd.option_id AND ops.store_id = cs.store_id', + array()); + + $minPrice = new Zend_Db_Expr("IF((@price:=IF(IF(ops.option_price_id>0, ops.price_type, opd.price_type)='fixed'," + . " IF(ops.option_price_id>0, ops.price, opd.price), ROUND(i.price * (IF(ops.option_price_id>0, " + . "ops.price, opd.price) / 100), 4))) AND o.is_require, @price,0)"); + $maxPrice = new Zend_Db_Expr("@price"); + $tierPrice = new Zend_Db_Expr("IF(i.tier_price IS NOT NULL, IF((@tier_price:=IF(IF(ops.option_price_id>0, " + . "ops.price_type, opd.price_type)='fixed', IF(ops.option_price_id>0, ops.price, opd.price), " + . "ROUND(i.tier_price * (IF(ops.option_price_id>0, ops.price, opd.price) / 100), 4))) AND o.is_require, " + . "@tier_price, 0), NULL)"); + + $select->columns(array( + 'min_price' => $minPrice, + 'max_price' => $maxPrice, + 'tier_price' => $tierPrice + )); + + $query = $select->insertFromSelect($coaTable); + $write->query($query); + + $select = $write->select() + ->from( + array($coaTable), + array( + 'entity_id', + 'customer_group_id', + 'website_id', + 'min_price' => 'SUM(min_price)', + 'max_price' => 'SUM(max_price)', + 'tier_price' => 'SUM(tier_price)', + )) + ->group(array('entity_id', 'customer_group_id', 'website_id')); + $query = $select->insertFromSelect($copTable); + $write->query($query); + + $table = array('i' => $this->_getDefaultFinalPriceTable()); + $select = $write->select() + ->join( + array('io' => $copTable), + 'i.entity_id = io.entity_id AND i.customer_group_id = io.customer_group_id' + .' AND i.website_id = io.website_id', + array()); + $select->columns(array( + 'min_price' => new Zend_Db_Expr('i.min_price + io.min_price'), + 'max_price' => new Zend_Db_Expr('i.max_price + io.max_price'), + 'tier_price' => new Zend_Db_Expr('IF(i.tier_price IS NOT NULL, i.tier_price + io.tier_price, NULL)'), + )); + $query = $select->crossUpdateFromSelect($table); + $write->query($query); + + $write->truncate($coaTable); + $write->truncate($copTable); + + return $this; + } + + /** + * Mode Final Prices index to primary temporary index table + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default + */ + protected function _movePriceDataToIndexTable() + { + $write = $this->_getWriteAdapter(); + $table = $this->_getDefaultFinalPriceTable(); + $select = $write->select() + ->from($table); + + $query = $select->insertFromSelect($this->getIdxTable()); + $write->query($query); + + $write->truncate($table); + + return $this; + } + + /** + * Retrieve table name for product tier price index + * + * @return string + */ + protected function _getTierPriceIndexTable() + { + return $this->getIdxTable($this->getValueTable('catalog/product', 'tier_price')); + } + + /** + * Register data required by product type process in event object + * + * @param Mage_Index_Model_Event $event + */ + public function registerEvent(Mage_Index_Model_Event $event) + {} +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Grouped.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Grouped.php new file mode 100644 index 0000000000..3d3f456247 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Grouped.php @@ -0,0 +1,122 @@ + + */ +class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Grouped + extends Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default +{ + /** + * Reindex temporary (price result data) for all products + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + public function reindexAll() + { + $this->_prepareGroupedProductPriceData(); + return $this; + } + + /** + * Reindex temporary (price result data) for defined product(s) + * + * @param int|array $entityIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + public function reindexEntity($entityIds) + { + $this->_prepareGroupedProductPriceData($entityIds); + + return $this; + } + + /** + * Calculate minimal and maximal prices for Grouped products + * Use calculated price for relation products + * + * @param int|array $entityIds the parent entity ids limitation + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Grouped + */ + protected function _prepareGroupedProductPriceData($entityIds = null) + { + $write = $this->_getWriteAdapter(); + $table = $this->getIdxTable(); + + $select = $write->select() + ->from(array('e' => $this->getTable('catalog/product')), 'entity_id') + ->join( + array('l' => $this->getTable('catalog/product_link')), + 'e.entity_id = l.product_id', + array()) + ->join( + array('cg' => $this->getTable('customer/customer_group')), + '', + array('customer_group_id')); + $this->_addWebsiteJoinToSelect($select, true); + $this->_addProductWebsiteJoinToSelect($select, 'cw.website_id', 'e.entity_id'); + $select->columns('website_id', 'cw') + ->join( + array('i' => $table), + 'i.entity_id = l.linked_product_id AND i.website_id = cw.website_id' + . ' AND i.customer_group_id = cg.customer_group_id', + array( + 'tax_class_id', + 'price' => new Zend_Db_Expr('NULL'), + 'final_price' => new Zend_Db_Expr('NULL'), + 'min_price' => new Zend_Db_Expr('MIN(i.min_price)'), + 'max_price' => new Zend_Db_Expr('MAX(i.max_price)'), + 'tier_price' => new Zend_Db_Expr('NULL') + )) + ->group(array('e.entity_id', 'i.customer_group_id', 'i.website_id')) + ->where('e.type_id=?', $this->getTypeId()) + ->where('l.link_type_id=?', Mage_Catalog_Model_Product_Link::LINK_TYPE_GROUPED); + + if (!is_null($entityIds)) { + $select->where('l.product_id IN(?)', $entityIds); + } + + /** + * Add additional external limitation + */ + Mage::dispatchEvent('prepare_catalog_product_price_index_select', array( + 'select' => $select, + 'entity_field' => new Zend_Db_Expr('e.entity_id'), + 'website_field' => new Zend_Db_Expr('cw.website_id'), + 'store_field' => new Zend_Db_Expr('cs.store_id') + )); + + $query = $select->insertFromSelect($table); + $write->query($query); + + return $this; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Interface.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Interface.php new file mode 100644 index 0000000000..899e6c7058 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price/Interface.php @@ -0,0 +1,59 @@ + + */ +interface Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface +{ + /** + * Reindex temporary (price result data) for all products + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + public function reindexAll(); + + /** + * Reindex temporary (price result data) for defined product(s) + * + * @param int|array $entityIds + * @param bool $hasOptions the entity has custom options flag + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + public function reindexEntity($entityIds); + + /** + * Register data required by product type process in event object + * + * @param Mage_Index_Model_Event $event + */ + public function registerEvent(Mage_Index_Model_Event $event); +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Link.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Link.php index 140277f9b5..0071da1e95 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Link.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Link.php @@ -30,17 +30,35 @@ * * @category Mage * @package Mage_Catalog - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Link extends Mage_Core_Model_Mysql4_Abstract { + /** + * Product Link Attributes Table + * + * @var string + */ protected $_attributesTable; + + /** + * Define main table name and attributes table + * + */ protected function _construct() { $this->_init('catalog/product_link', 'link_id'); $this->_attributesTable = $this->getTable('catalog/product_link_attribute'); } + /** + * Save Product Links process + * + * @param Mage_Catalog_Model_Product $product + * @param array $data + * @param int $typeId + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Link + */ public function saveProductLinks($product, $data, $typeId) { if (!is_array($data)) { @@ -61,18 +79,32 @@ public function saveProductLinks($product, $data, $typeId) $linkId = $this->_getWriteAdapter()->lastInsertId(); foreach ($attributes as $attributeInfo) { $attributeTable = $this->getAttributeTypeTable($attributeInfo['type']); - if ($attributeTable && isset($linkInfo[$attributeInfo['code']])) { + if ($attributeTable && isset($linkInfo[$attributeInfo['code']])) { $this->_getWriteAdapter()->insert($attributeTable, array( 'product_link_attribute_id' => $attributeInfo['id'], 'link_id' => $linkId, 'value' => $linkInfo[$attributeInfo['code']] )); - } + } } } + + /** + * Grouped product relations should be added to relation table + */ + if ($typeId == Mage_Catalog_Model_Product_Link::LINK_TYPE_GROUPED) { + + } + return $this; } + /** + * Retrieve product link attributes by link type + * + * @param int $typeId + * @return array + */ public function getAttributesByType($typeId) { $select = $this->_getReadAdapter()->select() @@ -90,7 +122,7 @@ public function getAttributeTypeTable($type) return $this->getTable('catalog/product_link_attribute_'.$type); } -/** + /** * Retrieve Required children ids * Return grouped array, ex array( * group => array(ids) @@ -144,4 +176,36 @@ public function getParentIdsByChild($childId, $typeId) return $parentIds; } -} \ No newline at end of file + + /** + * Save grouped product relations + * + * @param Mage_Catalog_Model_Product $parentProduct + * @param array $data + * @param int $typeId + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Link + */ + public function saveGroupedLinks($product, $data, $typeId) + { + // check for change relations + $select = $this->_getWriteAdapter()->select() + ->from($this->getMainTable(), array('linked_product_id')) + ->where('product_id=?', $product->getId()) + ->where('link_type_id=?', $typeId); + $old = $this->_getWriteAdapter()->fetchCol($select); + $new = array_keys($data); + + if (array_diff($old, $new) || array_diff($new, $old)) { + $product->setIsRelationsChanged(true); + } + + // save product links attributes + $this->saveProductLinks($product, $data, $typeId); + + // Grouped product relations should be added to relation table + Mage::getResourceSingleton('catalog/product_relation') + ->processRelations($product->getId(), $new); + + return $this; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Relation.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Relation.php new file mode 100644 index 0000000000..37eae63390 --- /dev/null +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Relation.php @@ -0,0 +1,84 @@ + + */ +class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Relation extends Mage_Core_Model_Mysql4_Abstract +{ + /** + * Initialize resource model and define main table + * + */ + protected function _construct() + { + $this->_init('catalog/product_relation', 'parent_id'); + } + + /** + * Save (rebuild) product relations + * + * @param int $parentId + * @param array $childIds + * @return Mage_Catalog_Model_Resource_Eav_Product_Relation + */ + public function processRelations($parentId, $childIds) + { + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable(), array('child_id')) + ->where('parent_id=?', $parentId); + $old = $this->_getReadAdapter()->fetchCol($select); + $new = $childIds; + + $insert = array_diff($new, $old); + $delete = array_diff($old, $new); + + if (!empty($insert)) { + $insertData = array(); + foreach ($insert as $childId) { + $insertData[] = array( + 'parent_id' => $parentId, + 'child_id' => $childId + ); + } + $this->_getWriteAdapter()->insertMultiple($this->getMainTable(), $insertData); + } + if (!empty($delete)) { + $where = join(' AND ', array( + $this->_getWriteAdapter()->quoteInto('parent_id=?', $parentId), + $this->_getWriteAdapter()->quoteInto('child_id IN(?)', $delete) + )); + $this->_getWriteAdapter()->delete($this->getMainTable(), $where); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Status.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Status.php index f28ab9329f..992f39ab27 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Status.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Status.php @@ -54,7 +54,7 @@ protected function _construct() * Retrieve product attribute (public method for status model) * * @param string $attributeCode - * @return Mage_Eav_Model_Entity_Attribute_Abstract + * @return Mage_Catalog_Model_Resource_Eav_Attribute */ public function getProductAttribute($attributeCode) { @@ -106,7 +106,7 @@ public function refreshEnabledIndex($productId, $storeId) $query = "INSERT INTO $indexTable SELECT - {$productId}, {$storeId}, IFNULL(t_v.value, t_v_default.value) + {$productId}, {$storeId}, IF(t_v.value_id>0, t_v.value, t_v_default.value) FROM {$visibilityTable} AS t_v_default LEFT JOIN {$visibilityTable} AS `t_v` @@ -118,7 +118,7 @@ public function refreshEnabledIndex($productId, $storeId) WHERE t_v_default.entity_id={$productId} AND t_v_default.attribute_id='{$visibilityAttributeId}' AND t_v_default.store_id=0 - AND (IFNULL(t_s.value, t_s_default.value)=".Mage_Catalog_Model_Product_Status::STATUS_ENABLED.")"; + AND (IF(t_s.value_id>0, t_s.value, t_s_default.value)=".Mage_Catalog_Model_Product_Status::STATUS_ENABLED.")"; $this->_getWriteAdapter()->query($query); return $this; @@ -205,7 +205,7 @@ public function getProductStatus($productIds, $storeId = null) $select = $this->_getReadAdapter()->select() ->from( array('t1' => $attributeTable), - array('entity_id', 'IFNULL(t2.value, t1.value) as value')) + array('entity_id', 'IF(t2.value_id>0, t2.value, t1.value) as value')) ->joinLeft( array('t2' => $attributeTable), $this->_getReadAdapter()->quoteInto('t1.entity_id = t2.entity_id AND t1.attribute_id = t2.attribute_id AND t2.store_id=?', $storeId), diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable.php index 5746983845..cbdfe41114 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable.php @@ -24,12 +24,13 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Configurable product type resource model * * @category Mage * @package Mage_Catalog - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable extends Mage_Core_Model_Mysql4_Abstract { @@ -43,23 +44,55 @@ protected function _construct() } /** - * Save product + * Save configurable product relations * - * @param int $mainProductId the parent id + * @param Mage_Catalog_Model_Product|int $mainProduct the parent id * @param array $productIds the children id array * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable */ - public function saveProducts($mainProductId, $productIds) + public function saveProducts($mainProduct, $productIds) { - $this->_getWriteAdapter()->delete($this->getMainTable(), - $this->_getWriteAdapter()->quoteInto('parent_id=?', $mainProductId) - ); - foreach ($productIds as $productId) { - $this->_getWriteAdapter()->insert($this->getMainTable(), array( - 'product_id' => $productId, - 'parent_id' => $mainProductId + $isProductInstance = false; + if ($mainProduct instanceof Mage_Catalog_Model_Product) { + $mainProductId = $mainProduct->getId(); + $isProductInstance = true; + } else { + $mainProductId = $mainProduct; + } + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable(), 'product_id') + ->where('parent_id=?', $mainProductId); + $old = $this->_getReadAdapter()->fetchCol($select); + + $insert = array_diff($productIds, $old); + $delete = array_diff($old, $productIds); + + if ((!empty($insert) || !empty($delete)) && $isProductInstance) { + $mainProduct->setIsRelationsChanged(true); + } + + if (!empty($delete)) { + $where = join(' AND ', array( + $this->_getWriteAdapter()->quoteInto('parent_id=?', $mainProductId), + $this->_getWriteAdapter()->quoteInto('product_id IN(?)', $delete) )); + $this->_getWriteAdapter()->delete($this->getMainTable(), $where); + } + if (!empty($insert)) { + $data = array(); + foreach ($insert as $childId) { + $data = array( + 'product_id' => $childId, + 'parent_id' => $mainProductId + ); + } + $this->_getWriteAdapter()->insertMultiple($this->getMainTable(), $data); } + + // configurable product relations should be added to relation table + Mage::getResourceSingleton('catalog/product_relation') + ->processRelations($mainProductId, $productIds); + return $this; } @@ -112,4 +145,4 @@ public function getParentIdsByChild($childId) return $parentIds; } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute.php index 7890ec1264..bff820b869 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute.php @@ -96,7 +96,7 @@ public function loadPrices($attribute) /** * Save Custom labels for Attribute name * - * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @param Mage_Catalog_Model_Product_Type_Configurable_Attribute $attribute * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute */ public function saveLabel($attribute) @@ -106,7 +106,10 @@ public function saveLabel($attribute) ->where('product_super_attribute_id=?', $attribute->getId()) ->where('store_id=?', (int)$attribute->getStoreId()); if ($valueId = $this->_getWriteAdapter()->fetchOne($select)) { - $this->_getWriteAdapter()->update($this->_labelTable,array('value'=>$attribute->getLabel()), + $this->_getWriteAdapter()->update($this->_labelTable,array( + 'use_default' => (int) $attribute->getUseDefault(), + 'value'=>$attribute->getLabel() + ), $this->_getWriteAdapter()->quoteInto('value_id=?', $valueId) ); } @@ -114,6 +117,7 @@ public function saveLabel($attribute) $this->_getWriteAdapter()->insert($this->_labelTable, array( 'product_super_attribute_id' => $attribute->getId(), 'store_id' => (int) $attribute->getStoreId(), + 'use_default' => (int) $attribute->getUseDefault(), 'value' => $attribute->getLabel() )); } @@ -123,150 +127,134 @@ public function saveLabel($attribute) /** * Save Options prices (Depends from price save scope) * - * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @param Mage_Catalog_Model_Product_Type_Configurable_Attribute $attribute * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Attribute */ public function savePrices($attribute) { - $newValues = $attribute->getValues(); + $write = $this->_getWriteAdapter(); + // define website id scope + if ($this->getCatalogHelper()->isPriceGlobal()) { + $websiteId = 0; + } else { + $websiteId = Mage::app()->getStore($attribute->getStoreId())->getWebsite()->getId(); + } - $oldValues = array(); - $valueIndexes = array(); - $select = $this->_getWriteAdapter()->select() + $values = $attribute->getValues(); + if (!is_array($values)) { + $values = array(); + } + + $new = array(); + $old = array(); + + // retrieve old values + $select = $write->select() ->from($this->_priceTable) - ->where('product_super_attribute_id=?', $attribute->getId()); - $query = $this->_getWriteAdapter()->query($select); - while ($row = $query->fetch()) { + ->where('product_super_attribute_id=?', $attribute->getId()) + ->where('website_id=?', $websiteId); + + $rowSet = $write->fetchAll($select); + foreach ($rowSet as $row) { $key = join('-', array($row['website_id'], $row['value_index'])); - $oldValues[$key] = $row; + if (!isset($old[$key])) { + $old[$key] = $row; + } else { + // delete invalid (duplicate row) + $write->delete($this->_priceTable, array('value_id' => $row['value_id'])); + } + } + + // prepare new values + foreach ($values as $v) { + if (empty($v['value_index'])) { + continue; + } + $key = join('-', array($websiteId, $v['value_index'])); + $new[$key] = array( + 'value_index' => $v['value_index'], + 'pricing_value' => $v['pricing_value'], + 'is_percent' => $v['is_percent'], + 'website_id' => $websiteId, + 'use_default' => !empty($v['use_default_value']) ? true : false + ); } - $delete = array(); $insert = array(); $update = array(); + $delete = array(); - foreach ($newValues as $value) { - $valueIndexes[$value['value_index']] = $value['value_index']; + foreach ($old as $k => $v) { + if (!isset($new[$k])) { + $delete[] = $v['value_id']; + } } + foreach ($new as $k => $v) { + $needInsert = false; + $needUpdate = false; + $needDelete = false; - if ($this->getCatalogHelper()->isPriceGlobal()) { - foreach ($oldValues as $row) { - if (!isset($valueIndexes[$row['value_index']])) { - $delete[] = $row['value_id']; - continue; - } + $isGlobal = true; + if (!$this->getCatalogHelper()->isPriceGlobal() && $websiteId != 0) { + $isGlobal = false; } - foreach ($newValues as $value) { - $valueObject = new Varien_Object($value); - $key = join('-', array(0, $value['value_index'])); - $pricingValue = $valueObject->getPricingValue(); - if ($pricingValue == '' || is_null($pricingValue)) { - $pricingValue = null; - } - else { - $pricingValue = Mage::app()->getLocale()->getNumber($pricingValue); - } - // update - if (isset($oldValues[$key])) { - $oldValue = $oldValues[$key]; - $update[$oldValue['value_id']] = array( - 'pricing_value' => $pricingValue, - 'is_percent' => intval($valueObject->getIsPercent()) - ); - } - // insert - else { - if (!empty($value['pricing_value'])) { - $insert[] = array( - 'product_super_attribute_id' => $attribute->getId(), - 'value_index' => $valueObject->getValueIndex(), - 'is_percent' => intval($valueObject->getIsPercent()), - 'pricing_value' => $pricingValue, - 'website_id' => 0 - ); - } - } - } - } - else { - $websiteId = Mage::app()->getStore($attribute->getStoreId())->getWebsiteId(); - foreach ($oldValues as $row) { - if (!isset($valueIndexes[$row['value_index']])) { - $delete[] = $row['value_id']; - continue; - } - } - foreach ($newValues as $value) { - $valueObject = new Varien_Object($value); - $key = join('-', array($websiteId, $value['value_index'])); + $hasValue = ($isGlobal && !empty($v['pricing_value'])) + || (!$isGlobal && !$v['use_default']); - $pricingValue = $valueObject->getPricingValue(); - if ($pricingValue == '' || is_null($pricingValue)) { - $pricingValue = null; - } - else { - $pricingValue = Mage::app()->getLocale()->getNumber($pricingValue); + if (isset($old[$k])) { + // data changed + $dataChanged = ($old[$k]['is_percent'] != $v['is_percent']) + || ($old[$k]['pricing_value'] != $v['pricing_value']); + if (!$hasValue) { + $needDelete = true; + } else if ($dataChanged) { + $needUpdate = true; } + } else if ($hasValue) { + $needInsert = true; + } - // update - if (isset($oldValues[$key])) { - $oldValue = $oldValues[$key]; + if (!$isGlobal && empty($v['pricing_value'])) { + $v['pricing_value'] = 0; + $v['is_percent'] = 0; + } - if ($websiteId && $valueObject->getUseDefaultValue()) { - $delete[] = $oldValue['value_id']; - } - else { - $update[$oldValue['value_id']] = array( - 'pricing_value' => $pricingValue, - 'is_percent' => intval($valueObject->getIsPercent()) - ); - } - } - // insert - else { - if ($websiteId && $valueObject->getUseDefaultValue()) { - continue; - } - $insert[] = array( - 'product_super_attribute_id' => $attribute->getId(), - 'value_index' => $valueObject->getValueIndex(), - 'is_percent' => intval($valueObject->getIsPercent()), - 'pricing_value' => $pricingValue, - 'website_id' => $websiteId - ); - } - $key = join('-', array(0, $value['value_index'])); - if (!isset($oldValues[$key])) { - $insert[] = array( - 'product_super_attribute_id' => $attribute->getId(), - 'value_index' => $valueObject->getValueIndex(), - 'is_percent' => 0, - 'pricing_value' => null, - 'website_id' => 0 - ); - } + if ($needInsert) { + $insert[] = array( + 'product_super_attribute_id' => $attribute->getId(), + 'value_index' => $v['value_index'], + 'is_percent' => $v['is_percent'], + 'pricing_value' => $v['pricing_value'], + 'website_id' => $websiteId + ); + } + if ($needUpdate) { + $update[$old[$k]['value_id']] = array( + 'is_percent' => $v['is_percent'], + 'pricing_value' => $v['pricing_value'] + ); + } + if ($needDelete) { + $delete[] = $old[$k]['value_id']; } } if (!empty($delete)) { - $where = $this->_getWriteAdapter()->quoteInto('value_id IN(?)', $delete); - $this->_getWriteAdapter()->delete($this->_priceTable, $where); + $where = $write->quoteInto('value_id IN(?)', $delete); + $write->delete($this->_priceTable, $where); } - if (!empty($update)) { - foreach ($update as $valueId => $valueData) { - $where = $this->_getWriteAdapter()->quoteInto('value_id=?', $valueId); - $this->_getWriteAdapter()->update($this->_priceTable, $valueData, $where); + foreach ($update as $valueId => $bind) { + $where = $write->quoteInto('value_id=?', $valueId); + $write->update($this->_priceTable, $bind, $where); } } - if (!empty($insert)) { - foreach ($insert as $valueData) { - $this->_getWriteAdapter()->insert($this->_priceTable, $valueData); - } + $write->insertMultiple($this->_priceTable, $insert); } + return $this; } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute/Collection.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute/Collection.php index 23eed6c67b..8dc84102b9 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute/Collection.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Type/Configurable/Attribute/Collection.php @@ -26,7 +26,7 @@ /** - * Catalog super product attribute collection + * Catalog Configurable Product Attribute Collection * * @category Mage * @package Mage_Catalog @@ -173,17 +173,17 @@ protected function _loadLabels() $select = $this->getConnection()->select() ->from(array('default'=>$this->_labelTable)) ->joinLeft( - array('store'=>$this->_labelTable), + array('store' => $this->_labelTable), 'store.product_super_attribute_id=default.product_super_attribute_id AND store.store_id='.$this->getStoreId(), array( - 'store_lebel'=>'value', + 'use_default' => new Zend_Db_Expr('IFNULL(store.use_default, default.use_default)'), 'label' => new Zend_Db_Expr('IFNULL(store.value, default.value)') - ) - ) + )) ->where('default.product_super_attribute_id IN (?)', array_keys($this->_items)) ->where('default.store_id=0'); foreach ($this->getConnection()->fetchAll($select) as $data) { - $this->getItemById($data['product_super_attribute_id'])->setLabel($data['label']); + $this->getItemById($data['product_super_attribute_id'])->setLabel($data['label']); + $this->getItemById($data['product_super_attribute_id'])->setUseDefault($data['use_default']); } } return $this; diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Website.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Website.php index 47ae4653a6..1bbe4c9ad8 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Website.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Website.php @@ -106,28 +106,32 @@ public function addProducts($websiteIds, $productIds) // Before adding of products we should remove it old rows with same ids $this->removeProducts($websiteIds, $productIds); + try { + foreach ($websiteIds as $websiteId) { + foreach ($productIds as $productId) { + if (!$productId) { + continue; + } + $this->_getWriteAdapter()->insert($this->getMainTable(), array( + 'product_id' => $productId, + 'website_id' => $websiteId + )); + } - foreach ($websiteIds as $websiteId) { - foreach ($productIds as $productId) { - if (!$productId) { - continue; + // Refresh product enabled index + $storeIds = Mage::app()->getWebsite($websiteId)->getStoreIds(); + foreach ($storeIds as $storeId) { + $store = Mage::app()->getStore($storeId); + $this->_getProductResource()->refreshEnabledIndex($store, $productIds); } - $this->_getWriteAdapter()->insert($this->getMainTable(), array( - 'product_id' => $productId, - 'website_id' => $websiteId - )); } - // Refresh product enabled index - $storeIds = Mage::app()->getWebsite($websiteId)->getStoreIds(); - foreach ($storeIds as $storeId) { - $store = Mage::app()->getStore($storeId); - $this->_getProductResource()->refreshEnabledIndex($store, $productIds); - } + $this->_getWriteAdapter()->commit(); + } + catch (Exception $e) { + $this->_getWriteAdapter()->rollBack(); + throw $e; } - - $this->_getWriteAdapter()->commit(); - return $this; } diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Setup.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Setup.php index 440aff8680..019bd39ba9 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Setup.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Setup.php @@ -35,6 +35,38 @@ class Mage_Catalog_Model_Resource_Eav_Mysql4_Setup extends Mage_Eav_Model_Entity_Setup { + /** + * Prepare catalog attribute values to save + * + * @param array $attr + * @return array + */ + protected function _prepareValues($attr) + { + $data = parent::_prepareValues($attr); + $data = array_merge($data, array( + 'frontend_input_renderer' => $this->_getValue($attr, 'input_renderer', ''), + 'source_model' => $this->_getValue($attr, 'source', ''), + 'is_global' => $this->_getValue($attr, 'global', 1), + 'is_visible' => $this->_getValue($attr, 'visible', 1), + 'is_searchable' => $this->_getValue($attr, 'searchable', 0), + 'is_filterable' => $this->_getValue($attr, 'filterable', 0), + 'is_comparable' => $this->_getValue($attr, 'comparable', 0), + 'is_visible_on_front' => $this->_getValue($attr, 'visible_on_front', 0), + 'is_html_allowed_on_front' => $this->_getValue($attr, 'is_html_allowed_on_front', 0), + 'is_visible_in_advanced_search' + => $this->_getValue($attr, 'visible_in_advanced_search', 0), + 'is_used_for_price_rules' => $this->_getValue($attr, 'used_for_price_rules', 1), + 'is_filterable_in_search' => $this->_getValue($attr, 'filterable_in_search', 0), + 'used_in_product_listing' => $this->_getValue($attr, 'used_in_product_listing', 0), + 'used_for_sort_by' => $this->_getValue($attr, 'used_for_sort_by', 0), + 'apply_to' => $this->_getValue($attr, 'apply_to', ''), + 'position' => $this->_getValue($attr, 'position', 0), + 'is_configurable' => $this->_getValue($attr, 'is_configurable', 1) + )); + return $data; + } + /** * Enter description here... * @@ -47,6 +79,8 @@ public function getDefaultEntities() 'entity_model' => 'catalog/category', 'attribute_model' => 'catalog/resource_eav_attribute', 'table' => 'catalog/category', + 'additional_attribute_table' => 'catalog/eav_attribute', + 'entity_attribute_collection' => 'catalog/category_attribute_collection', 'attributes' => array( 'name' => array( 'type' => 'varchar', @@ -77,7 +111,7 @@ public function getDefaultEntities() 'source' => 'eav/entity_attribute_source_boolean', 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE, 'visible' => true, - 'required' => false, + 'required' => true, 'user_defined' => false, 'default' => '', 'searchable' => false, @@ -517,13 +551,36 @@ public function getDefaultEntities() 'comparable' => false, 'visible_on_front' => false, 'unique' => false, - ) + ), + 'available_sort_by' => array( + 'input' => 'multiselect', + 'type' => 'text', + 'label' => 'Available Product Listing Sort by', + 'source' => 'catalog/category_attribute_source_sortby', + 'backend' => 'catalog/category_attribute_backend_sortby', + 'required' => true, + 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE, + 'visible' => true, + 'input_renderer'=> 'adminhtml/catalog_category_helper_sortby_available', + ), + 'default_sort_by' => array( + 'input' => 'select', + 'label' => 'Default Product Listing Sort by', + 'source' => 'catalog/category_attribute_source_sortby', + 'backend' => 'catalog/category_attribute_backend_sortby', + 'required' => true, + 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE, + 'visible' => true, + 'input_renderer'=> 'adminhtml/catalog_category_helper_sortby_default', + ), ), ), 'catalog_product' => array( 'entity_model' => 'catalog/product', 'attribute_model' => 'catalog/resource_eav_attribute', 'table' => 'catalog/product', + 'additional_attribute_table' => 'catalog/eav_attribute', + 'entity_attribute_collection' => 'catalog/product_attribute_collection', 'attributes' => array( 'name' => array( 'type' => 'varchar', @@ -543,6 +600,8 @@ public function getDefaultEntities() 'comparable' => false, 'visible_on_front' => false, 'visible_in_advanced_search' => true, + 'used_in_product_listing' => true, + 'used_for_sort_by' => true, 'unique' => false, ), 'description' => array( @@ -583,6 +642,7 @@ public function getDefaultEntities() 'comparable' => true, 'visible_on_front' => false, 'visible_in_advanced_search' => true, + 'used_in_product_listing' => true, 'unique' => false, ), 'sku' => array( @@ -624,6 +684,8 @@ public function getDefaultEntities() 'comparable' => false, 'visible_on_front' => false, 'visible_in_advanced_search' => true, + 'used_in_product_listing' => true, + 'used_for_sort_by' => true, 'unique' => false, 'apply_to' => 'simple,configurable,virtual', ), @@ -645,6 +707,7 @@ public function getDefaultEntities() 'filterable' => false, 'comparable' => false, 'visible_on_front' => false, + 'used_in_product_listing' => true, 'unique' => false, 'apply_to' => 'simple,configurable,virtual', ), @@ -666,6 +729,7 @@ public function getDefaultEntities() 'filterable' => false, 'comparable' => false, 'visible_on_front' => false, + 'used_in_product_listing' => true, 'unique' => false, 'apply_to' => 'simple,configurable,virtual', ), @@ -687,6 +751,7 @@ public function getDefaultEntities() 'filterable' => false, 'comparable' => false, 'visible_on_front' => false, + 'used_in_product_listing' => true, 'unique' => false, 'apply_to' => 'simple,configurable,virtual', ), @@ -709,7 +774,7 @@ public function getDefaultEntities() 'comparable' => false, 'visible_on_front' => false, 'unique' => false, - 'apply_to' => 'simple,configurable,virtual', + 'apply_to' => 'simple,virtual', ), 'weight' => array( 'type' => 'decimal', @@ -851,6 +916,7 @@ public function getDefaultEntities() 'filterable' => false, 'comparable' => false, 'visible_on_front' => false, + 'used_in_product_listing' => true, 'unique' => false, ), 'thumbnail' => array( @@ -871,6 +937,7 @@ public function getDefaultEntities() 'filterable' => false, 'comparable' => false, 'visible_on_front' => false, + 'used_in_product_listing' => true, 'unique' => false, ), 'media_gallery' => array( @@ -882,7 +949,7 @@ public function getDefaultEntities() 'source' => '', 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL, 'visible' => true, - 'required' => true, + 'required' => false, 'user_defined' => false, 'default' => '', 'searchable' => false, @@ -927,6 +994,7 @@ public function getDefaultEntities() 'filterable' => false, 'comparable' => false, 'visible_on_front' => false, + 'used_for_price_rules' => false, 'unique' => false, 'apply_to' => 'simple,configurable,virtual', ), @@ -969,6 +1037,7 @@ public function getDefaultEntities() 'filterable' => false, 'comparable' => false, 'visible_on_front' => false, + 'used_in_product_listing' => true, 'unique' => false, ), 'news_to_date' => array( @@ -989,6 +1058,7 @@ public function getDefaultEntities() 'filterable' => false, 'comparable' => false, 'visible_on_front' => false, + 'used_in_product_listing' => true, 'unique' => false, ), 'gallery' => array( @@ -1030,6 +1100,7 @@ public function getDefaultEntities() 'comparable' => false, 'visible_on_front' => false, 'visible_in_advanced_search' => false, + 'used_in_product_listing' => true, 'unique' => false, ), 'tax_class_id' => array( @@ -1051,56 +1122,15 @@ public function getDefaultEntities() 'comparable' => false, 'visible_on_front' => false, 'visible_in_advanced_search' => true, + 'used_in_product_listing' => true, 'unique' => false, 'apply_to' => 'simple,configurable,virtual', ), -// 'price_includes_tax' => array( -// 'group' => 'Prices', -// 'type' => 'int', -// 'backend' => '', -// 'frontend' => '', -// 'label' => 'Price Includes Tax', -// 'input' => 'select', -// 'class' => '', -// 'source' => 'tax/price_source_includes', -// 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_WEBSITE, -// 'visible' => true, -// 'required' => false, -// 'user_defined' => false, -// 'default' => '', -// 'searchable' => true, -// 'filterable' => false, -// 'comparable' => false, -// 'visible_on_front' => false, -// 'visible_in_advanced_search' => true, -// 'unique' => false, -// ), - 'price' => array( - 'group' => 'Prices', - 'type' => 'decimal', - 'backend' => 'catalog/product_attribute_backend_price', - 'frontend' => '', - 'label' => 'Price', - 'input' => 'price', - 'class' => 'validate-number', - 'source' => '', - 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_WEBSITE, - 'visible' => true, - 'required' => true, - 'user_defined' => false, - 'default' => '', - 'searchable' => true, - 'filterable' => true, - 'comparable' => false, - 'visible_on_front' => false, - 'visible_in_advanced_search' => true, - 'unique' => false, - 'apply_to' => 'simple,configurable', - ), 'url_key' => array( 'label' => 'URL key', 'backend' => 'catalog/product_attribute_backend_urlkey', 'required' => false, + 'used_in_product_listing' => true, ), 'url_path' => array( 'type' => 'varchar', @@ -1140,7 +1170,7 @@ public function getDefaultEntities() 'comparable' => false, 'visible_on_front' => false, 'unique' => false, - 'apply_to' => 'simple,configurable', + 'apply_to' => 'simple,configurable,virtual', ), 'visibility' => array( 'group' => 'General', @@ -1305,6 +1335,57 @@ public function getDefaultEntities() 'visible_on_front' => false, 'visible_in_advanced_search' => false, 'unique' => false, + ), + 'required_options' => array( + 'type' => 'static', + 'visible' => false, + 'default' => false, + 'used_in_product_listing' => true, + ), + 'has_options' => array( + 'type' => 'static', + 'visible' =>false, + 'default' => false, + ), + 'image_label' => array( + 'type' => 'varchar', + 'label' => 'Image Label', + 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE, + 'visible' => false, + 'required' => false, + 'searchable' => false, + 'is_configurable' => false, + 'used_in_product_listing' => true, + ), + 'small_image_label' => array( + 'type' => 'varchar', + 'label' => 'Small Image Label', + 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE, + 'visible' => false, + 'required' => false, + 'searchable' => false, + 'is_configurable' => false, + 'used_in_product_listing' => true, + ), + 'thumbnail_label' => array( + 'type' => 'varchar', + 'label' => 'Thumbnail Label', + 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE, + 'visible' => false, + 'required' => false, + 'searchable' => false, + 'is_configurable' => false, + 'used_in_product_listing' => true, + ), + 'created_at' => array( + 'type' => 'static', + 'backend' => 'eav/entity_attribute_backend_time_created', + 'visible' => false, + ), + 'updated_at' => array( + 'type' => 'static', + 'backend' => 'eav/entity_attribute_backend_time_updated', + 'visible' => false, ) ), ), diff --git a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Url.php b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Url.php index b1b644e2c0..dbb66a7410 100644 --- a/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Url.php +++ b/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Url.php @@ -151,6 +151,29 @@ public function getRewriteByRequestPath($requestPath, $storeId) return $rewrite; } + /** + * Validate array of request paths. Return first not used path in case if validations passed + * + * @param array $paths + * @param int $storeId + * @return false | string + */ + public function checkRequestPaths($paths, $storeId) + { + $select = $this->_getWriteAdapter()->select() + ->from($this->getMainTable(), 'request_path') + ->where('store_id=?', $storeId) + ->where('request_path IN (?)', $paths); + $data = $this->_getWriteAdapter()->fetchCol($select); + $paths = array_diff($paths, $data); + if (empty($paths)) { + return false; + } else { + reset($paths); + return current($paths); + } + } + /** * Prepare rewrites for condition * @@ -336,7 +359,7 @@ protected function _getCategoryAttribute($attributeCode, $categoryIds, $storeId) ->where('entity_id IN(?)', $categoryIds); } else { $select = $this->_getWriteAdapter()->select() - ->from(array('t1'=>$attributeTable), array('entity_id', 'IFNULL(t2.value, t1.value) as value')) + ->from(array('t1'=>$attributeTable), array('entity_id', 'IF(t2.value_id>0, t2.value, t1.value) as value')) ->joinLeft( array('t2'=>$attributeTable), $this->_getWriteAdapter()->quoteInto('t1.entity_id = t2.entity_id AND t1.attribute_id = t2.attribute_id AND t2.store_id=?', $storeId), @@ -470,7 +493,7 @@ public function _getProductAttribute($attributeCode, $productIds, $storeId) } else { $select = $this->_getWriteAdapter()->select() - ->from(array('t1'=>$attributeTable), array('entity_id', 'IFNULL(t2.value, t1.value) as value')) + ->from(array('t1'=>$attributeTable), array('entity_id', 'IF(t2.value_id>0, t2.value, t1.value) as value')) ->joinLeft( array('t2'=>$attributeTable), $this->_getWriteAdapter()->quoteInto('t1.entity_id = t2.entity_id AND t1.attribute_id = t2.attribute_id AND t2.store_id=?', $storeId), @@ -555,7 +578,8 @@ protected function _prepareStoreRootCategories($stores) */ protected function _getCategories($categoryIds, $storeId = null, $path = null) { - $isActiveAttribute = Mage::getModel('eav/entity_attribute')->loadByCode('catalog_category', 'is_active'); + //$isActiveAttribute = Mage::getModel('eav/entity_attribute')->loadByCode('catalog_category', 'is_active'); + $isActiveAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_category', 'is_active'); $categories = array(); if (!is_array($categoryIds)) { @@ -563,7 +587,7 @@ protected function _getCategories($categoryIds, $storeId = null, $path = null) } $select = $this->_getWriteAdapter()->select() - ->from(array('main_table'=>$this->getTable('catalog/category')), array('main_table.entity_id', 'main_table.parent_id', 'is_active'=>'IFNULL(c.value, d.value)', 'main_table.path')); + ->from(array('main_table'=>$this->getTable('catalog/category')), array('main_table.entity_id', 'main_table.parent_id', 'is_active'=>'IF(c.value_id>0, c.value, d.value)', 'main_table.path')); if (is_null($path)) { $select->where('main_table.entity_id IN(?)', $categoryIds); @@ -880,13 +904,33 @@ public function clearCategoryProduct($storeId) */ public function deleteCategoryProductRewrites($categoryId, $productIds) { - $condition = $this->_getWriteAdapter()->quoteInto('category_id=?', $categoryId); - $condition = $this->_getWriteAdapter()->quoteInto( - $condition.' AND product_id IN (?)', - $productIds - ); + $this->deleteCatagoryProductStoreRewrites($categoryId, $productIds); + return $this; + } + + /** + * Delete URL rewrites for category products of specific store \ + * + * @param int $categoryId + * @param array|int|null $productIds + * @param null|int $storeId + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Url + */ + public function deleteCategoryProductStoreRewrites($categoryId, $productIds=null, $storeId=null) + { + $adapter = $this->_getWriteAdapter(); + $condition = $adapter->quoteInto('category_id=?', $categoryId); + if (empty($productIds)) { + $condition.= ' AND product_id IS NOT NULL'; + } else { + $condition.= $adapter->quoteInto(' AND product_id IN (?)', $productIds); + } + if ($storeId !== null) { + $condition.= $adapter->quoteInto(' AND store_id IN(?)', $storeId); + } $this->_getWriteAdapter()->delete($this->getMainTable(), $condition); return $this; + } /** diff --git a/app/code/core/Mage/Catalog/Model/Url.php b/app/code/core/Mage/Catalog/Model/Url.php index eacbb2bc67..dbe0184312 100644 --- a/app/code/core/Mage/Catalog/Model/Url.php +++ b/app/code/core/Mage/Catalog/Model/Url.php @@ -237,7 +237,9 @@ protected function _refreshProductRewrite(Varien_Object $product, Varien_Object $idPath = $this->generatePath('id', $product, $category); $targetPath = $this->generatePath('target', $product, $category); - $requestPath = $this->generatePath('request', $product, $category); + //$requestPath = $this->generatePath('request', $product, $category); + $requestPath = $this->getProductRequestPath($product, $category); + $categoryId = null; $updateKeys = true; if ($category->getUrlPath()) { @@ -280,9 +282,17 @@ protected function _refreshCategoryProductRewrites(Varien_Object $category) $originalRewrites = $this->_rewrites; $process = true; $lastEntityId = 0; + $firstIteration = true; while ($process == true) { $products = $this->getResource()->getProductsByCategory($category, $lastEntityId); if (!$products) { + if ($firstIteration) { + $this->getResource()->deleteCategoryProductStoreRewrites( + $category->getId(), + array(), + $category->getStoreId() + ); + } $process = false; break; } @@ -292,6 +302,7 @@ protected function _refreshCategoryProductRewrites(Varien_Object $category) foreach ($products as $product) { $this->_refreshProductRewrite($product, $category); } + $firstIteration = false; unset($products); } $this->_rewrites = $originalRewrites; @@ -434,13 +445,20 @@ public function refreshProductRewrites($storeId) */ public function getUnusedPath($storeId, $requestPath, $idPath) { + if (strpos($idPath, 'product') !== false) { + $suffix = $this->getProductUrlSuffix($storeId); + } else { + $suffix = $this->getCategoryUrlSuffix($storeId); + } if (empty($requestPath)) { $requestPath = '-'; - } - elseif ($requestPath == $this->getProductUrlSuffix($storeId)) { - $requestPath = '-' . $this->getProductUrlSuffix($storeId); + } elseif ($requestPath == $suffix) { + $requestPath = '-' . $suffix; } + /** + * Validate maximum length of request path + */ if (strlen($requestPath) > self::MAX_REQUEST_PATH_LENGTH + self::ALLOWED_REQUEST_PATH_OVERFLOW) { $requestPath = substr($requestPath, 0, self::MAX_REQUEST_PATH_LENGTH); } @@ -461,11 +479,9 @@ public function getUnusedPath($storeId, $requestPath, $idPath) $this->_rewrite = $rewrite; return $requestPath; } - // retrieve url_suffix for product urls - $productUrlSuffix = $this->getProductUrlSuffix($storeId); // match request_url abcdef1234(-12)(.html) pattern $match = array(); - if (!preg_match('#^([0-9a-z/-]+?)(-([0-9]+))?('.preg_quote($productUrlSuffix).')?$#i', $requestPath, $match)) { + if (!preg_match('#^([0-9a-z/-]+?)(-([0-9]+))?('.preg_quote($suffix).')?$#i', $requestPath, $match)) { return $this->getUnusedPath($storeId, '-', $idPath); } $requestPath = $match[1].(isset($match[3])?'-'.($match[3]+1):'-1').(isset($match[4])?$match[4]:''); @@ -498,6 +514,76 @@ public function getCategoryUrlSuffix($storeId) return Mage::helper('catalog/category')->getCategoryUrlSuffix($storeId); } + /** + * Get unique product request path + * + * @param Varien_Object $product + * @param Varien_Object $category + * @return string + */ + public function getProductRequestPath($product, $category) + { + if ($product->getUrlKey() == '') { + $urlKey = $this->getProductModel()->formatUrlKey($product->getName()); + } else { + $urlKey = $this->getProductModel()->formatUrlKey($product->getUrlKey()); + } + $storeId = $category->getStoreId(); + $suffix = $this->getProductUrlSuffix($storeId); + $idPath = $this->generatePath('id', $product, $category); + /** + * Prepare product base request path + */ + if ($category->getUrlPath()) { + $categoryUrl = Mage::helper('catalog/category')->getCategoryUrlPath($category->getUrlPath(), false, $storeId); + $requestPath = $categoryUrl . '/' . $urlKey; + } else { + $requestPath = $urlKey; + } + + if (strlen($requestPath) > self::MAX_REQUEST_PATH_LENGTH + self::ALLOWED_REQUEST_PATH_OVERFLOW) { + $requestPath = substr($requestPath, 0, self::MAX_REQUEST_PATH_LENGTH); + } + + $this->_rewrite = null; + /** + * Chack $requestPath should be unique + */ + if (isset($this->_rewrites[$idPath])) { + $this->_rewrite = $this->_rewrites[$idPath]; + $existingRequestPath = $this->_rewrites[$idPath]->getRequestPath(); + $existingRequestPath = str_replace($suffix, '', $existingRequestPath); + + if ($existingRequestPath == $requestPath) { + return $requestPath.$suffix; + } + /** + * Check if existing request past can be used + */ + if (strpos($existingRequestPath, $requestPath) !== false) { + $existingRequestPath = str_replace($requestPath, '', $existingRequestPath); + if (preg_match('#^-([0-9]+)$#i', $existingRequestPath)) { + return $this->_rewrites[$idPath]->getRequestPath(); + } + } + } + /** + * Check 2 variants: $requestPath and $requestPath . '-' . $productId + */ + $validatedPath = $this->getResource()->checkRequestPaths( + array($requestPath.$suffix, $requestPath.'-'.$product->getId().$suffix), + $storeId + ); + + if ($validatedPath) { + return $validatedPath; + } + /** + * Use unique path generator + */ + return $this->getUnusedPath($storeId, $requestPath.$suffix, $idPath); + } + /** * Generate either id path, request path or target path for product and/or category * diff --git a/app/code/core/Mage/Catalog/etc/adminhtml.xml b/app/code/core/Mage/Catalog/etc/adminhtml.xml new file mode 100644 index 0000000000..7a92c22d03 --- /dev/null +++ b/app/code/core/Mage/Catalog/etc/adminhtml.xml @@ -0,0 +1,117 @@ + + + + + + Catalog + 30 + + + Mage_Catalog + + + + Manage Products + adminhtml/catalog_product/ + + + Manage Categories + adminhtml/catalog_category/ + + + Attributes + + + Manage Attributes + adminhtml/catalog_product_attribute/ + + + Manage Attribute Sets + adminhtml/catalog_product_set/ + + + + + URL Rewrite Management + adminhtml/urlrewrite/index + + + + + + + + + + + + + + Catalog Section + + + + + + + Catalog + 30 + + + Attributes + + + Manage Attributes + + + Manage Attribute Sets + + + + + Manage Categories + + + Manage Products + + + Update Attributes + + + Search + + + Url Rewrite Management + + + + + + + + diff --git a/app/code/core/Mage/Catalog/etc/config.xml b/app/code/core/Mage/Catalog/etc/config.xml index ca298ca945..e9ed7ea01c 100644 --- a/app/code/core/Mage/Catalog/etc/config.xml +++ b/app/code/core/Mage/Catalog/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.72 + 1.4.0.0.10 @@ -160,102 +160,38 @@ Mage_Catalog_Model_Resource_Eav_Mysql4 - - catalog_product_entity - - - catalog_category_entity - - - catalog_category_product - - - catalog_category_product_index - - - catalog_compare_item - - - - catalog_product_website - - - - catalog_product_enabled_index - - - - catalog_product_link_type - - - - catalog_product_link - - - - catalog_product_link_attribute - - - catalog_product_link_attribute_decimal - - - catalog_product_link_attribute_int - - - catalog_product_link_attribute_varchar - - - - catalog_product_super_attribute - - - catalog_product_super_attribute_label - - - catalog_product_super_attribute_pricing - - - catalog_product_super_link - - - - catalog_product_entity_tier_price - - - - catalog_product_entity_media_gallery - - - - catalog_product_entity_media_gallery_value - - - - catalog_product_option - - - catalog_product_option_price - - - catalog_product_option_title - - - catalog_product_option_type_value - - - catalog_product_option_type_price - - - catalog_product_option_type_title - - - - catalog_category_flat - - - - catalog_product_flat - + catalog_product_entity + catalog_category_entity + catalog_category_product + catalog_category_product_index + catalog_compare_item + catalog_product_website + catalog_product_enabled_index + catalog_product_link_type + catalog_product_link + catalog_product_link_attribute + catalog_product_link_attribute_decimal + catalog_product_link_attribute_int + catalog_product_link_attribute_varchar + catalog_product_super_attribute + catalog_product_super_attribute_label + catalog_product_super_attribute_pricing + catalog_product_super_link + catalog_product_entity_tier_price + catalog_product_entity_media_gallery + catalog_product_entity_media_gallery_value + catalog_product_option + catalog_product_option_price + catalog_product_option_title + catalog_product_option_type_value + catalog_product_option_type_price + catalog_product_option_type_title + catalog_category_flat + catalog_product_flat + catalog_eav_attribute + catalog_product_relation + catalog_product_index_eav + catalog_product_index_price @@ -265,164 +201,153 @@ Mage_Catalog Mage_Catalog_Model_Resource_Eav_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - - Mage_Catalog_Block - + + + + catalog/product_indexer_eav + + + catalog/product_indexer_price + + + catalog/indexer_url + + + catalog/product_indexer_flat + + + catalog/category_indexer_flat + + + catalog/category_indexer_product + + + - - - - singleton - catalog/observer - storeAdd - - - singleton - catalog/product_flat_observer - storeAdd - - - - - - - singleton - catalog/observer - storeEdit - - - singleton - catalog/product_flat_observer - storeEdit - - - - - - - singleton - catalog/observer - storeDelete - - - singleton - catalog/product_flat_observer - storeDelete - - - - - - - singleton - catalog/observer - storeGroupSave - - - singleton - catalog/product_flat_observer - storeGroupSave - - - - - - - singleton - catalog/observer - categoryMove - - - - - - - singleton - catalog/observer - catalogProductImportAfter - - - singleton - catalog/product_flat_observer - catalogProductImportAfter - - - - - - - singleton - catalog/observer - categorySaveAfter - - - - - - - singleton - catalog/product_flat_observer - customerGroupSaveAfter - - - - - - - singleton - catalog/product_flat_observer - catalogProductSaveAfter - - - - - - - singleton - catalog/product_flat_observer - catalogProductStatusUpdate - - - - - - - singleton - catalog/product_flat_observer - catalogProductWebsiteUpdate - - - - - - - singleton - catalog/product_flat_observer - catalogEntityAttributeSaveAfter - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - singleton catalog/observer catalogProductCompareClean @@ -437,6 +362,7 @@ Simple Product catalog/product_type_simple 0 + 10 Grouped Product @@ -446,6 +372,8 @@ + 50 + catalog/product_indexer_price_grouped Configurable Product @@ -456,11 +384,14 @@ + 30 + catalog/product_indexer_price_configurable Virtual Product catalog/product_type_virtual 0 + 20 @@ -544,96 +475,41 @@ + + + + sku + + + + + + + url_key + + + + + + status + + + + + + + + visibility + + + + + + + + - - - Catalog - 30 - - - Mage_Catalog - - - - Manage Products - adminhtml/catalog_product/ - - - Manage Categories - adminhtml/catalog_category/ - - - Attributes - - - Manage Attributes - adminhtml/catalog_product_attribute/ - - - Manage Attribute Sets - adminhtml/catalog_product_set/ - - - - - URL Rewrite Management - adminhtml/urlrewrite/index - - - - - - - - - - - - - - Catalog Section - - - - - - - Catalog - 30 - - - Attributes - - - Manage Attributes - - - Manage Attribute Sets - - - - - Manage Categories - - - Manage Products - - - Update Attributes - - - Search - - - Url Rewrite Management - - - - - - - @@ -733,6 +609,13 @@ + + + + + + + @@ -779,326 +662,14 @@ --> - - - &and - @at - ©c - ®r - Àa - Ãa - Âa - Äa - Ã…a - Æae - Çc - Èe - Ée - Ëe - ÃŒi - Ãi - ÃŽi - Ãi - Ã’o - Óo - Ôo - Õo - Öo - Øo - Ùu - Úu - Ûu - Ãœu - Ãy - ßss - à a - áa - âa - äa - Ã¥a - æae - çc - èe - ée - êe - ëe - ìi - Ãi - îi - ïi - òo - óo - ôo - õo - öo - øo - ùu - úu - ûu - üu - ýy - þp - ÿy - Ä€a - Äa - Ä‚a - ăa - Ä„a - Ä…a - Ćc - ćc - Ĉc - ĉc - ÄŠc - Ä‹c - ÄŒc - Äc - ÄŽd - Äd - Äd - Ä‘d - Ä’e - Ä“e - Ä”e - Ä•e - Ä–e - Ä—e - Ęe - Ä™e - Äše - Ä›e - Äœg - Äg - Äžg - ÄŸg - Ä g - Ä¡g - Ä¢g - Ä£g - Ĥh - Ä¥h - Ħh - ħh - Ĩi - Ä©i - Īi - Ä«i - Ĭi - Äi - Ä®i - įi - Ä°i - ıi - IJij - ijij - Ä´j - ĵj - Ķk - Ä·k - ĸk - Ĺl - ĺl - Ä»l - ļl - Ľl - ľl - Ä¿l - Å€l - Ål - Å‚l - Ńn - Å„n - Å…n - ņn - Ňn - ňn - ʼnn - ÅŠn - Å‹n - ÅŒo - Åo - ÅŽo - Åo - Åo - Å‘o - Å’oe - Å“oe - Å”r - Å•r - Å–r - Å—r - Řr - Å™r - Åšs - Å›s - Åœs - Ås - Åžs - ÅŸs - Å s - Å¡s - Å¢t - Å£t - Ťt - Å¥t - Ŧt - ŧt - Ũu - Å©u - Ūu - Å«u - Ŭu - Åu - Å®u - ůu - Å°u - űu - Ųu - ųu - Å´w - ŵw - Ŷy - Å·y - Ÿy - Źz - źz - Å»z - żz - Žz - žz - Å¿z - Æe - Æ’f - Æ o - Æ¡o - Ưu - Æ°u - Ça - ÇŽa - Çi - Çi - Ç‘o - Ç’o - Ç“u - Ç”u - Ç•u - Ç–u - Ç—u - ǘu - Ç™u - Çšu - Ç›u - Çœu - Ǻa - Ç»a - Ǽae - ǽae - Ǿo - Ç¿o - É™e - Ðjo - Єe - Іi - Їi - Ða - Бb - Ð’v - Гg - Дd - Еe - Жzh - Зz - Иi - Йj - Кk - Лl - Ðœm - Ðn - Оo - Пp - Ð r - Сs - Тt - Уu - Фf - Ð¥h - Цc - Чch - Шsh - Щsch - Ъ- - Ыy - Ь- - Ðje - Юju - Яja - аa - бb - вv - гg - дd - еe - жzh - зz - иi - йj - кk - лl - мm - нn - оo - пp - Ñ€r - Ñs - Ñ‚t - уu - Ñ„f - Ñ…h - цc - чch - шsh - щsch - ÑŠ- - Ñ‹y - ÑŒ- - Ñje - ÑŽju - Ñja - Ñ‘jo - Ñ”e - Ñ–i - Ñ—i - Òg - Ò‘g - ×a - בb - ×’g - דd - ×”h - וv - ×–z - ×—h - טt - ×™i - ךk - ×›k - לl - ×m - מm - ןn - × n - סs - ×¢e - ×£p - פp - ×¥C - צc - קq - רr - שw - תt - â„¢tm - - + + + + + 0 2 * * * + catalog/product_indexer_price::reindexAll + + + diff --git a/app/code/core/Mage/Catalog/etc/widget.xml b/app/code/core/Mage/Catalog/etc/widget.xml new file mode 100644 index 0000000000..c0199db194 --- /dev/null +++ b/app/code/core/Mage/Catalog/etc/widget.xml @@ -0,0 +1,110 @@ + + + + + + Catalog New Products List + List of Products that are marked as New + + + 1 + 1 + 5 + Number of Products to Display + text + + + 1 + catalog/product/new.phtml + + + Cache Lifetime (Seconds) + 86400 by default, if not set + 1 + text + + + + + + Catalog Product Link + Link to a Specified Product + + + 1 + 1 + Product + + label + adminhtml/catalog_product_widget_chooser + + 10 + + + 1 + Anchor Text + If empty, the Product Name will be used + text + + + 1 + Anchor Title + text + + + + + + Catalog Category Link + Link to a Specified Category + + + 1 + 1 + Category + + label + adminhtml/catalog_category_widget_chooser + + 10 + + + 1 + Anchor Text + If empty, the Category Name will be used + text + + + 1 + Anchor Title + text + + + + + diff --git a/app/code/core/Mage/Catalog/etc/wsdl.xml b/app/code/core/Mage/Catalog/etc/wsdl.xml index 73b8ebd58f..f9b1f10599 100644 --- a/app/code/core/Mage/Catalog/etc/wsdl.xml +++ b/app/code/core/Mage/Catalog/etc/wsdl.xml @@ -374,9 +374,10 @@ - + + @@ -396,6 +397,7 @@ + @@ -407,6 +409,7 @@ + @@ -415,6 +418,7 @@ + @@ -422,6 +426,7 @@ + @@ -455,15 +460,17 @@ - + + - + + @@ -472,6 +479,7 @@ + @@ -481,6 +489,7 @@ + @@ -497,6 +506,7 @@ + @@ -507,6 +517,7 @@ + @@ -515,6 +526,7 @@ + @@ -523,6 +535,7 @@ + @@ -533,6 +546,7 @@ + @@ -543,6 +557,7 @@ + @@ -552,6 +567,7 @@ + @@ -648,6 +664,7 @@ + @@ -657,6 +674,7 @@ + @@ -665,6 +683,7 @@ + diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-install-1.4.0.0.0.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-install-1.4.0.0.0.php new file mode 100644 index 0000000000..4202ec7a32 --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-install-1.4.0.0.0.php @@ -0,0 +1,800 @@ +startSetup(); + +if (!$installer->tableExists($installer->getTable('catalog_category_entity'))) { + +$installer->run(" + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_category_entity')}; +CREATE TABLE {$installer->getTable('catalog_category_entity')} ( + `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', + `parent_id` int(10) 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', + `path` varchar(255) NOT NULL, + `position` int(11) NOT NULL, + `level` int(11) NOT NULL, + `children_count` int(11) NOT NULL, + PRIMARY KEY (`entity_id`), + KEY `IDX_LEVEL` (`level`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Category Entities'; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_category_entity_datetime')}; +CREATE TABLE {$installer->getTable('catalog_category_entity_datetime')} ( + `value_id` int(11) NOT NULL auto_increment, + `entity_type_id` smallint(5) 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` datetime NOT NULL default '0000-00-00 00:00:00', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_BASE` (`entity_type_id`,`entity_id`,`attribute_id`,`store_id`), + KEY `FK_ATTRIBUTE_DATETIME_ENTITY` (`entity_id`), + KEY `FK_CATALOG_CATEGORY_ENTITY_DATETIME_ATTRIBUTE` (`attribute_id`), + KEY `FK_CATALOG_CATEGORY_ENTITY_DATETIME_STORE` (`store_id`), + CONSTRAINT `FK_CATALOG_CATEGORY_ENTITY_DATETIME_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES {$installer->getTable('eav_attribute')} (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_CATEGORY_ENTITY_DATETIME_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES {$installer->getTable('catalog_category_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_CATEGORY_ENTITY_DATETIME_STORE` FOREIGN KEY (`store_id`) REFERENCES {$installer->getTable('core_store')} (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_category_entity_decimal')}; +CREATE TABLE {$installer->getTable('catalog_category_entity_decimal')} ( + `value_id` int(11) NOT NULL auto_increment, + `entity_type_id` smallint(5) 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` decimal(12,4) NOT NULL default '0.0000', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_BASE` (`entity_type_id`,`entity_id`,`attribute_id`,`store_id`), + KEY `FK_ATTRIBUTE_DECIMAL_ENTITY` (`entity_id`), + KEY `FK_CATALOG_CATEGORY_ENTITY_DECIMAL_ATTRIBUTE` (`attribute_id`), + KEY `FK_CATALOG_CATEGORY_ENTITY_DECIMAL_STORE` (`store_id`), + CONSTRAINT `FK_CATALOG_CATEGORY_ENTITY_DECIMAL_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES {$installer->getTable('eav_attribute')} (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_CATEGORY_ENTITY_DECIMAL_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES {$installer->getTable('catalog_category_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_CATEGORY_ENTITY_DECIMAL_STORE` FOREIGN KEY (`store_id`) REFERENCES {$installer->getTable('core_store')} (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_category_entity_int')}; +CREATE TABLE {$installer->getTable('catalog_category_entity_int')} ( + `value_id` int(11) NOT NULL auto_increment, + `entity_type_id` smallint(5) 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` int(11) NOT NULL default '0', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_BASE` (`entity_type_id`,`entity_id`,`attribute_id`,`store_id`), + KEY `FK_ATTRIBUTE_INT_ENTITY` (`entity_id`), + KEY `FK_CATALOG_CATEGORY_EMTITY_INT_ATTRIBUTE` (`attribute_id`), + KEY `FK_CATALOG_CATEGORY_EMTITY_INT_STORE` (`store_id`), + CONSTRAINT `FK_CATALOG_CATEGORY_EMTITY_INT_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES {$installer->getTable('eav_attribute')} (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_CATEGORY_EMTITY_INT_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES {$installer->getTable('catalog_category_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_CATEGORY_EMTITY_INT_STORE` FOREIGN KEY (`store_id`) REFERENCES {$installer->getTable('core_store')} (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_category_entity_text')}; +CREATE TABLE {$installer->getTable('catalog_category_entity_text')} ( + `value_id` int(11) NOT NULL auto_increment, + `entity_type_id` smallint(5) 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` text NOT NULL, + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_BASE` (`entity_type_id`,`entity_id`,`attribute_id`,`store_id`), + KEY `FK_ATTRIBUTE_TEXT_ENTITY` (`entity_id`), + KEY `FK_CATALOG_CATEGORY_ENTITY_TEXT_ATTRIBUTE` (`attribute_id`), + KEY `FK_CATALOG_CATEGORY_ENTITY_TEXT_STORE` (`store_id`), + CONSTRAINT `FK_CATALOG_CATEGORY_ENTITY_TEXT_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES {$installer->getTable('eav_attribute')} (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_CATEGORY_ENTITY_TEXT_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES {$installer->getTable('catalog_category_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_CATEGORY_ENTITY_TEXT_STORE` FOREIGN KEY (`store_id`) REFERENCES {$installer->getTable('core_store')} (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_category_entity_varchar')}; +CREATE TABLE {$installer->getTable('catalog_category_entity_varchar')} ( + `value_id` int(11) NOT NULL auto_increment, + `entity_type_id` smallint(5) 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` varchar(255) NOT NULL default '', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_BASE` USING BTREE (`entity_type_id`,`entity_id`,`attribute_id`,`store_id`), + KEY `FK_ATTRIBUTE_VARCHAR_ENTITY` (`entity_id`), + KEY `FK_CATALOG_CATEGORY_ENTITY_VARCHAR_ATTRIBUTE` (`attribute_id`), + KEY `FK_CATALOG_CATEGORY_ENTITY_VARCHAR_STORE` (`store_id`), + CONSTRAINT `FK_CATALOG_CATEGORY_ENTITY_VARCHAR_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES {$installer->getTable('eav_attribute')} (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_CATEGORY_ENTITY_VARCHAR_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES {$installer->getTable('catalog_category_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_CATEGORY_ENTITY_VARCHAR_STORE` FOREIGN KEY (`store_id`) REFERENCES {$installer->getTable('core_store')} (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_category_product')}; +CREATE TABLE {$installer->getTable('catalog_category_product')} ( + `category_id` int(10) unsigned NOT NULL default '0', + `product_id` int(10) unsigned NOT NULL default '0', + `position` int(10) unsigned NOT NULL default '0', + UNIQUE KEY `UNQ_CATEGORY_PRODUCT` (`category_id`,`product_id`), + KEY `CATALOG_CATEGORY_PRODUCT_CATEGORY` (`category_id`), + KEY `CATALOG_CATEGORY_PRODUCT_PRODUCT` (`product_id`), + CONSTRAINT `CATALOG_CATEGORY_PRODUCT_CATEGORY` FOREIGN KEY (`category_id`) REFERENCES {$installer->getTable('catalog_category_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `CATALOG_CATEGORY_PRODUCT_PRODUCT` FOREIGN KEY (`product_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_compare_item')}; +CREATE TABLE {$installer->getTable('catalog_compare_item')} ( + `catalog_compare_item_id` int(11) unsigned NOT NULL auto_increment, + `visitor_id` int(11) unsigned NOT NULL default '0', + `customer_id` int(11) unsigned default NULL, + `product_id` int(11) unsigned NOT NULL default '0', + `store_id` smallint(5) unsigned DEFAULT NULL, + PRIMARY KEY (`catalog_compare_item_id`), + KEY `FK_CATALOG_COMPARE_ITEM_CUSTOMER` (`customer_id`), + KEY `FK_CATALOG_COMPARE_ITEM_PRODUCT` (`product_id`), + KEY `IDX_VISITOR_PRODUCTS` (`visitor_id`,`product_id`), + KEY `IDX_CUSTOMER_PRODUCTS` (`customer_id`,`product_id`), + KEY `FK_CATALOG_COMPARE_ITEM_STORE` (`store_id`), + CONSTRAINT `FK_CATALOG_COMPARE_ITEM_CUSTOMER` FOREIGN KEY (`customer_id`) REFERENCES {$installer->getTable('customer_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_COMPARE_ITEM_PRODUCT` FOREIGN KEY (`product_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_COMPARE_ITEM_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core/store')}` (`store_id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_bundle_option')}; +CREATE TABLE {$installer->getTable('catalog_product_bundle_option')} ( + `option_id` int(10) unsigned NOT NULL auto_increment, + `product_id` int(10) unsigned NOT NULL default '0', + PRIMARY KEY (`option_id`), + KEY `FK_catalog_product_bundle_option` (`product_id`), + CONSTRAINT `FK_catalog_product_bundle_option` FOREIGN KEY (`product_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_bundle_option_link')}; +CREATE TABLE {$installer->getTable('catalog_product_bundle_option_link')} ( + `link_id` int(10) unsigned NOT NULL auto_increment, + `option_id` int(10) unsigned NOT NULL default '0', + `product_id` int(10) unsigned NOT NULL default '0', + `discount` decimal(10,4) unsigned default NULL, + PRIMARY KEY (`link_id`), + KEY `FK_catalog_product_bundle_option_link` (`option_id`), + KEY `FK_catalog_product_bundle_option_link_entity` (`product_id`), + CONSTRAINT `FK_catalog_product_bundle_option_link` FOREIGN KEY (`option_id`) REFERENCES {$installer->getTable('catalog_product_bundle_option')} (`option_id`) ON DELETE CASCADE, + CONSTRAINT `FK_catalog_product_bundle_option_link_entity` FOREIGN KEY (`product_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_bundle_option_value')}; +CREATE TABLE {$installer->getTable('catalog_product_bundle_option_value')} ( + `value_id` int(10) unsigned NOT NULL auto_increment, + `store_id` smallint(5) unsigned NOT NULL default '0', + `option_id` int(10) unsigned NOT NULL default '0', + `label` varchar(255) default NULL, + `position` smallint(5) unsigned NOT NULL default '0', + PRIMARY KEY (`value_id`), + KEY `FK_catalog_product_bundle_option_label` (`option_id`), + CONSTRAINT `FK_catalog_product_bundle_option_label` FOREIGN KEY (`option_id`) REFERENCES {$installer->getTable('catalog_product_bundle_option')} (`option_id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_entity')}; +CREATE TABLE {$installer->getTable('catalog_product_entity')} ( + `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', + `type_id` varchar(32) NOT NULL DEFAULT 'simple', + `sku` varchar (64) default NULL, + `has_options` smallint(1) NOT NULL DEFAULT '0', + `required_options` tinyint(1) 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', + PRIMARY KEY (`entity_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_ENTITY_TYPE` (`entity_type_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_ATTRIBUTE_SET_ID` (`attribute_set_id`), + KEY `sku` (`sku`), + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_ATTRIBUTE_SET_ID` FOREIGN KEY (`attribute_set_id`) REFERENCES {$installer->getTable('eav_attribute_set')} (`attribute_set_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES {$installer->getTable('eav_entity_type')} (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Product Entities'; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_entity_datetime')}; +CREATE TABLE {$installer->getTable('catalog_product_entity_datetime')} ( + `value_id` int(11) NOT NULL auto_increment, + `entity_type_id` smallint(5) 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` datetime NOT NULL default '0000-00-00 00:00:00', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`,`store_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_DATETIME_ATTRIBUTE` (`attribute_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_DATETIME_STORE` (`store_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_DATETIME_PRODUCT_ENTITY` (`entity_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_DATETIME_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES {$installer->getTable('eav_attribute')} (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_DATETIME_PRODUCT_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_DATETIME_STORE` FOREIGN KEY (`store_id`) REFERENCES {$installer->getTable('core_store')} (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_entity_decimal')}; +CREATE TABLE {$installer->getTable('catalog_product_entity_decimal')} ( + `value_id` int(11) NOT NULL auto_increment, + `entity_type_id` smallint(5) 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` decimal(12,4) NOT NULL default '0.0000', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`,`store_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_DECIMAL_STORE` (`store_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_DECIMAL_PRODUCT_ENTITY` (`entity_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_DECIMAL_ATTRIBUTE` (`attribute_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_DECIMAL_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES {$installer->getTable('eav_attribute')} (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_DECIMAL_PRODUCT_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_DECIMAL_STORE` FOREIGN KEY (`store_id`) REFERENCES {$installer->getTable('core_store')} (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_entity_gallery')}; +CREATE TABLE {$installer->getTable('catalog_product_entity_gallery')} ( + `value_id` int(11) NOT NULL auto_increment, + `entity_type_id` smallint(5) 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', + `position` int(11) NOT NULL default '0', + `value` varchar(255) NOT NULL default '', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_BASE` (`entity_type_id`,`entity_id`,`attribute_id`,`store_id`), + KEY `FK_ATTRIBUTE_GALLERY_ENTITY` (`entity_id`), + KEY `FK_CATALOG_CATEGORY_ENTITY_GALLERY_ATTRIBUTE` (`attribute_id`), + KEY `FK_CATALOG_CATEGORY_ENTITY_GALLERY_STORE` (`store_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_GALLERY_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES {$installer->getTable('eav_attribute')} (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_GALLERY_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_GALLERY_STORE` FOREIGN KEY (`store_id`) REFERENCES {$installer->getTable('core_store')} (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_entity_int')}; +CREATE TABLE {$installer->getTable('catalog_product_entity_int')} ( + `value_id` int(11) NOT NULL auto_increment, + `entity_type_id` mediumint(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` int(11) NOT NULL default '0', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`,`store_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_INT_ATTRIBUTE` (`attribute_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_INT_STORE` (`store_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_INT_PRODUCT_ENTITY` (`entity_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_INT_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES {$installer->getTable('eav_attribute')} (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_INT_PRODUCT_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_INT_STORE` FOREIGN KEY (`store_id`) REFERENCES {$installer->getTable('core_store')} (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_entity_text')}; +CREATE TABLE {$installer->getTable('catalog_product_entity_text')} ( + `value_id` int(11) NOT NULL auto_increment, + `entity_type_id` mediumint(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` text NOT NULL, + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`,`store_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_TEXT_ATTRIBUTE` (`attribute_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_TEXT_STORE` (`store_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_TEXT_PRODUCT_ENTITY` (`entity_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_TEXT_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES {$installer->getTable('eav_attribute')} (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_TEXT_PRODUCT_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_TEXT_STORE` FOREIGN KEY (`store_id`) REFERENCES {$installer->getTable('core_store')} (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_entity_tier_price')}; +CREATE TABLE {$installer->getTable('catalog_product_entity_tier_price')} ( + `value_id` int(11) NOT NULL auto_increment, + `entity_id` int(10) unsigned NOT NULL default '0', + `all_groups` tinyint (1)unsigned NOT NULL DEFAULT '1', + `customer_group_id` smallint(5) unsigned NOT NULL default '0', + `qty` decimal(12,4) NOT NULL default 1, + `value` decimal(12,4) NOT NULL default '0.0000', + `website_id` smallint(5) unsigned NOT NULL, + PRIMARY KEY (`value_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_TIER_PRICE_PRODUCT_ENTITY` (`entity_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_TIER_PRICE_GROUP` (`customer_group_id`), + KEY `FK_CATALOG_PRODUCT_TIER_WEBSITE` (`website_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_TIER_PRICE_PRODUCT_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_TIER_PRICE_GROUP` FOREIGN KEY (`customer_group_id`) REFERENCES {$installer->getTable('customer_group')} (`customer_group_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_TIER_WEBSITE` FOREIGN KEY (`website_id`) REFERENCES `{$installer->getTable('core_website')}` (`website_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_entity_varchar')}; +CREATE TABLE {$installer->getTable('catalog_product_entity_varchar')} ( + `value_id` int(11) NOT NULL auto_increment, + `entity_type_id` mediumint(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` varchar(255) NOT NULL default '', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`,`store_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_VARCHAR_ATTRIBUTE` (`attribute_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_VARCHAR_STORE` (`store_id`), + KEY `FK_CATALOG_PRODUCT_ENTITY_VARCHAR_PRODUCT_ENTITY` (`entity_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_VARCHAR_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES {$installer->getTable('eav_attribute')} (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_VARCHAR_PRODUCT_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENTITY_VARCHAR_STORE` FOREIGN KEY (`store_id`) REFERENCES {$installer->getTable('core_store')} (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_link')}; +CREATE TABLE {$installer->getTable('catalog_product_link')} ( + `link_id` int(11) unsigned NOT NULL auto_increment, + `product_id` int(10) unsigned NOT NULL default '0', + `linked_product_id` int(10) unsigned NOT NULL default '0', + `link_type_id` tinyint(3) unsigned NOT NULL default '0', + PRIMARY KEY (`link_id`), + KEY `FK_LINK_PRODUCT` (`product_id`), + KEY `FK_LINKED_PRODUCT` (`linked_product_id`), + KEY `FK_PRODUCT_LINK_TYPE` (`link_type_id`), + CONSTRAINT `FK_PRODUCT_LINK_LINKED_PRODUCT` FOREIGN KEY (`linked_product_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_PRODUCT_LINK_PRODUCT` FOREIGN KEY (`product_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_PRODUCT_LINK_TYPE` FOREIGN KEY (`link_type_id`) REFERENCES {$installer->getTable('catalog_product_link_type')} (`link_type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Related products'; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_link_attribute')}; +CREATE TABLE {$installer->getTable('catalog_product_link_attribute')} ( + `product_link_attribute_id` smallint(6) unsigned NOT NULL auto_increment, + `link_type_id` tinyint(3) unsigned NOT NULL default '0', + `product_link_attribute_code` varchar(32) NOT NULL default '', + `data_type` varchar(32) NOT NULL default '', + PRIMARY KEY (`product_link_attribute_id`), + KEY `FK_ATTRIBUTE_PRODUCT_LINK_TYPE` (`link_type_id`), + CONSTRAINT `FK_ATTRIBUTE_PRODUCT_LINK_TYPE` FOREIGN KEY (`link_type_id`) REFERENCES {$installer->getTable('catalog_product_link_type')} (`link_type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Attributes for product link'; + +insert into {$installer->getTable('catalog_product_link_attribute')}(`product_link_attribute_id`,`link_type_id`,`product_link_attribute_code`,`data_type`) values (1,2,'qty','decimal'),(2,1,'position','int'),(3,4,'position','int'),(4,5,'position','int'),(6,1,'qty','decimal'),(7,3,'position','int'),(8,3,'qty','decimal'); + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_link_attribute_decimal')}; +CREATE TABLE {$installer->getTable('catalog_product_link_attribute_decimal')} ( + `value_id` int(11) unsigned NOT NULL auto_increment, + `product_link_attribute_id` smallint(6) unsigned default NULL, + `link_id` int(11) unsigned default NULL, + `value` decimal(12,4) NOT NULL default '0.0000', + PRIMARY KEY (`value_id`), + KEY `FK_DECIMAL_PRODUCT_LINK_ATTRIBUTE` (`product_link_attribute_id`), + KEY `FK_DECIMAL_LINK` (`link_id`), + CONSTRAINT `FK_DECIMAL_LINK` FOREIGN KEY (`link_id`) REFERENCES {$installer->getTable('catalog_product_link')} (`link_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_DECIMAL_PRODUCT_LINK_ATTRIBUTE` FOREIGN KEY (`product_link_attribute_id`) REFERENCES {$installer->getTable('catalog_product_link_attribute')} (`product_link_attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Decimal attributes values'; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_link_attribute_int')}; +CREATE TABLE {$installer->getTable('catalog_product_link_attribute_int')} ( + `value_id` int(11) unsigned NOT NULL auto_increment, + `product_link_attribute_id` smallint(6) unsigned default NULL, + `link_id` int(11) unsigned default NULL, + `value` int(11) NOT NULL default '0', + PRIMARY KEY (`value_id`), + KEY `FK_INT_PRODUCT_LINK_ATTRIBUTE` (`product_link_attribute_id`), + KEY `FK_INT_PRODUCT_LINK` (`link_id`), + CONSTRAINT `FK_INT_PRODUCT_LINK` FOREIGN KEY (`link_id`) REFERENCES {$installer->getTable('catalog_product_link')} (`link_id`) ON DELETE CASCADE, + CONSTRAINT `FK_INT_PRODUCT_LINK_ATTRIBUTE` FOREIGN KEY (`product_link_attribute_id`) REFERENCES {$installer->getTable('catalog_product_link_attribute')} (`product_link_attribute_id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_link_attribute_varchar')}; +CREATE TABLE {$installer->getTable('catalog_product_link_attribute_varchar')} ( + `value_id` int(11) unsigned NOT NULL auto_increment, + `product_link_attribute_id` smallint(6) unsigned NOT NULL default '0', + `link_id` int(11) unsigned default NULL, + `value` varchar(255) NOT NULL default '', + PRIMARY KEY (`value_id`), + KEY `FK_VARCHAR_PRODUCT_LINK_ATTRIBUTE` (`product_link_attribute_id`), + KEY `FK_VARCHAR_LINK` (`link_id`), + CONSTRAINT `FK_VARCHAR_LINK` FOREIGN KEY (`link_id`) REFERENCES {$installer->getTable('catalog_product_link')} (`link_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_VARCHAR_PRODUCT_LINK_ATTRIBUTE` FOREIGN KEY (`product_link_attribute_id`) REFERENCES {$installer->getTable('catalog_product_link_attribute')} (`product_link_attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Varchar attributes values'; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_link_type')}; +CREATE TABLE {$installer->getTable('catalog_product_link_type')} ( + `link_type_id` tinyint(3) unsigned NOT NULL auto_increment, + `code` varchar(32) NOT NULL default '', + PRIMARY KEY (`link_type_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Types of product link(Related, superproduct, bundles)'; + +insert into {$installer->getTable('catalog_product_link_type')}(`link_type_id`,`code`) values (1,'relation'),(2,'bundle'),(3,'super'),(4,'up_sell'),(5,'cross_sell'); + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_super_attribute')}; +CREATE TABLE {$installer->getTable('catalog_product_super_attribute')} ( + `product_super_attribute_id` int(10) unsigned NOT NULL auto_increment, + `product_id` int(10) unsigned NOT NULL default '0', + `attribute_id` smallint(5) unsigned NOT NULL default '0', + `position` smallint(5) unsigned NOT NULL default '0', + PRIMARY KEY (`product_super_attribute_id`), + KEY `FK_SUPER_PRODUCT_ATTRIBUTE_PRODUCT` (`product_id`), + CONSTRAINT `FK_SUPER_PRODUCT_ATTRIBUTE_PRODUCT` FOREIGN KEY (`product_id`) REFERENCES {$installer->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_super_attribute_label')}; +CREATE TABLE {$installer->getTable('catalog_product_super_attribute_label')} ( + `value_id` int(10) unsigned NOT NULL auto_increment, + `product_super_attribute_id` int(10) unsigned NOT NULL default '0', + `store_id` smallint(5) unsigned NOT NULL default '0', + `use_default` tinyint(1) unsigned DEFAULT '0', + `value` varchar(255) character set utf8 NOT NULL default '', + PRIMARY KEY (`value_id`), + UNIQUE KEY `UNQ_ATTRIBUTE_STORE` (`product_super_attribute_id`,`store_id`), + KEY `FK_SUPER_PRODUCT_ATTRIBUTE_LABEL` (`product_super_attribute_id`), + KEY `FK_CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL_STORE` (`store_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL_ATTRIBUTE` FOREIGN KEY (`product_super_attribute_id`) REFERENCES `{$installer->getTable('catalog_product_super_attribute')}` (`product_super_attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core/store')}` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_super_attribute_pricing')}; +CREATE TABLE {$installer->getTable('catalog_product_super_attribute_pricing')} ( + `value_id` int(10) unsigned NOT NULL auto_increment, + `product_super_attribute_id` int(10) unsigned NOT NULL default '0', + `value_index` varchar(255) character set utf8 NOT NULL default '', + `is_percent` tinyint(1) unsigned default '0', + `pricing_value` decimal(10,4) default NULL, + `website_id` smallint(5) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`value_id`), + KEY `FK_SUPER_PRODUCT_ATTRIBUTE_PRICING` (`product_super_attribute_id`), + KEY `FK_CATALOG_PRODUCT_SUPER_PRICE_WEBSITE` (`website_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_SUPER_PRICE_WEBSITE` FOREIGN KEY (`website_id`) REFERENCES `{$installer->getTable('core/website')}` (`website_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_SUPER_PRODUCT_ATTRIBUTE_PRICING` FOREIGN KEY (`product_super_attribute_id`) REFERENCES `{$installer->getTable('catalog_product_super_attribute')}` (`product_super_attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_super_link')}; +CREATE TABLE {$installer->getTable('catalog_product_super_link')} ( + `link_id` int(10) unsigned NOT NULL auto_increment, + `product_id` int(10) unsigned NOT NULL default '0', + `parent_id` int(10) unsigned NOT NULL default '0', + PRIMARY KEY (`link_id`), + KEY `FK_SUPER_PRODUCT_LINK_PARENT` (`parent_id`), + KEY `FK_catalog_product_super_link` (`product_id`), + CONSTRAINT `FK_SUPER_PRODUCT_LINK_ENTITY` FOREIGN KEY (`product_id`) REFERENCES `{$installer->getTable('catalog_product_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_SUPER_PRODUCT_LINK_PARENT` FOREIGN KEY (`parent_id`) REFERENCES `{$installer->getTable('catalog_product_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_category_product_index')}; +CREATE TABLE `{$installer->getTable('catalog_category_product_index')}` ( + `category_id` int(10) unsigned NOT NULL default '0', + `product_id` int(10) unsigned NOT NULL default '0', + `position` int(10) unsigned NOT NULL default '0', + `is_parent` tinyint(1) unsigned NOT NULL default '0', + `store_id` smallint(5) unsigned NOT NULL DEFAULT '0', + `visibility` tinyint(3) unsigned NOT NULL, + UNIQUE KEY `UNQ_CATEGORY_PRODUCT` (`category_id`,`product_id`,`is_parent`,`store_id`), + KEY `FK_CATALOG_CATEGORY_PRODUCT_INDEX_CATEGORY_ENTITY` (`category_id`), + KEY `IDX_JOIN` (`product_id`,`store_id`,`category_id`,`visibility`), + KEY `IDX_BASE` (`store_id`,`category_id`,`visibility`,`is_parent`,`position`), + CONSTRAINT `FK_CATALOG_CATEGORY_PRODUCT_INDEX_PRODUCT_ENTITY` FOREIGN KEY (`product_id`) REFERENCES `{$installer->getTable('catalog_product_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_CATEGORY_PRODUCT_INDEX_CATEGORY_ENTITY` FOREIGN KEY (`category_id`) REFERENCES `{$installer->getTable('catalog_category_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATEGORY_PRODUCT_INDEX_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core/store')}` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_enabled_index')}; +CREATE TABLE `{$installer->getTable('catalog_product_enabled_index')}` ( + `product_id` int(10) unsigned NOT NULL default '0', + `store_id` smallint(5) unsigned NOT NULL default '0', + `visibility` smallint(5) unsigned NOT NULL default '0', + UNIQUE KEY `UNQ_PRODUCT_STORE` (`product_id`,`store_id`), + KEY `IDX_PRODUCT_VISIBILITY_IN_STORE` (`product_id`,`store_id`, `visibility`), + CONSTRAINT `FK_CATALOG_PRODUCT_ENABLED_INDEX_PRODUCT_ENTITY` FOREIGN KEY (`product_id`) REFERENCES `{$installer->getTable('catalog_product_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_ENABLED_INDEX_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core_store')}` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +update {$installer->getTable('eav_entity_attribute')} set `sort_order`=10 where `attribute_id`=(select `attribute_id` from {$installer->getTable('eav_attribute')} where `attribute_code`='tier_price'); + +-- DROP TABLE IF EXISTS {$installer->getTable('catalog_product_website')}; +CREATE TABLE {$installer->getTable('catalog_product_website')} ( + `product_id` INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `website_id` SMALLINT(5) UNSIGNED NOT NULL, + PRIMARY KEY (`product_id`, `website_id`), + KEY `FK_CATALOG_PRODUCT_WEBSITE_WEBSITE` (`website_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_WEBSITE_WEBSITE` FOREIGN KEY (`website_id`) REFERENCES `{$installer->getTable('core/website')}` (`website_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_WEBSITE_PRODUCT_PRODUCT` FOREIGN KEY (`product_id`) REFERENCES `{$installer->getTable('catalog_product_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT = FIXED; + +-- DROP TABLE IF EXISTS `{$installer->getTable('catalog_product_entity_media_gallery')}`; +CREATE TABLE `{$installer->getTable('catalog_product_entity_media_gallery')}` ( + `value_id` int(11) unsigned NOT NULL auto_increment, + `attribute_id` smallint(5) unsigned NOT NULL default '0', + `entity_id` int(10) unsigned NOT NULL default '0', + `value` varchar(255) default NULL, + PRIMARY KEY (`value_id`), + KEY `FK_CATALOG_PRODUCT_MEDIA_GALLERY_ATTRIBUTE` (`attribute_id`), + KEY `FK_CATALOG_PRODUCT_MEDIA_GALLERY_ENTITY` (`entity_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_MEDIA_GALLERY_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav_attribute')}` (`attribute_id`) ON DELETE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_MEDIA_GALLERY_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES `{$installer->getTable('catalog_product_entity')}` (`entity_id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Catalog product media gallery'; + +-- DROP TABLE IF EXISTS `{$installer->getTable('catalog_product_entity_media_gallery_value')}`; +CREATE TABLE `{$installer->getTable('catalog_product_entity_media_gallery_value')}` ( + `value_id` int(11) unsigned NOT NULL default '0', + `store_id` smallint(5) unsigned NOT NULL default '0', + `label` varchar(255) default NULL, + `position` int(11) unsigned default NULL, + `disabled` tinyint(1) unsigned NOT NULL default '0', + PRIMARY KEY (`value_id`,`store_id`), + KEY `FK_CATALOG_PRODUCT_MEDIA_GALLERY_VALUE_STORE` (`store_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_MEDIA_GALLERY_VALUE_GALLERY` FOREIGN KEY (`value_id`) REFERENCES `{$installer->getTable('catalog_product_entity_media_gallery')}` (`value_id`) ON DELETE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_MEDIA_GALLERY_VALUE_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core_store')}` (`store_id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Catalog product media gallery values'; + +"); + +$installer->getConnection()->dropColumn($installer->getTable('eav_attribute'), 'use_in_super_product'); + +$installer->getConnection()->addColumn($installer->getTable('core_url_rewrite'), 'category_id', 'int unsigned NULL AFTER `store_id`'); +$installer->getConnection()->addColumn($installer->getTable('core_url_rewrite'), 'product_id', 'int unsigned NULL AFTER `category_id`'); +$installer->getConnection()->addConstraint('FK_CORE_URL_REWRITE_CATEGORY', $installer->getTable('core_url_rewrite'), 'category_id', $installer->getTable('catalog_category_entity'), 'entity_id'); +$installer->getConnection()->addConstraint('FK_CORE_URL_REWRITE_PRODUCT', $installer->getTable('core_url_rewrite'), 'product_id', $installer->getTable('catalog_product_entity'), 'entity_id'); + +$installer->run(" +UPDATE `{$installer->getTable('eav_attribute')}` SET `position` = 1 WHERE `position` = 0 AND `attribute_code` != 'price'; + +-- DROP TABLE IF EXISTS `{$installer->getTable('catalog/product_option')}`; +CREATE TABLE `{$installer->getTable('catalog/product_option')}` ( + `option_id` int(10) unsigned NOT NULL auto_increment, + `product_id` int(10) unsigned NOT NULL default '0', + `type` varchar(50) NOT NULL default '', + `is_require` tinyint(1) NOT NULL default '1', + `sku` varchar(64) NOT NULL default '', + `max_characters` int(10) unsigned default NULL, + `file_extension` varchar(50) default NULL, + `image_size_x` smallint(5) unsigned NOT NULL, + `image_size_y` smallint(5) unsigned NOT NULL, + `sort_order` int(10) unsigned NOT NULL default '0', + PRIMARY KEY (`option_id`), + KEY `CATALOG_PRODUCT_OPTION_PRODUCT` (`product_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_OPTION_PRODUCT` FOREIGN KEY (`product_id`) REFERENCES `{$installer->getTable('catalog/product')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE +)ENGINE=InnoDB default CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('catalog/product_option_price')}`; +CREATE TABLE `{$installer->getTable('catalog/product_option_price')}` ( + `option_price_id` int(10) unsigned NOT NULL auto_increment, + `option_id` int(10) unsigned NOT NULL default '0', + `store_id` smallint(5) unsigned NOT NULL default '0', + `price` decimal(12,4) NOT NULL default '0.00', + `price_type` enum('fixed', 'percent') NOT NULL default 'fixed', + PRIMARY KEY (`option_price_id`), + KEY `CATALOG_PRODUCT_OPTION_PRICE_OPTION` (`option_id`), + KEY `CATALOG_PRODUCT_OPTION_TITLE_STORE` (`store_id`), + KEY `IDX_CATALOG_PRODUCT_OPTION_PRICE_SI_OI` (`store_id`,`option_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_OPTION_PRICE_OPTION` FOREIGN KEY (`option_id`) REFERENCES `{$installer->getTable('catalog/product_option')}` (`option_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_OPTION_PRICE_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core/store')}` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +)ENGINE=InnoDB default CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('catalog/product_option_title')}`; +CREATE TABLE `{$installer->getTable('catalog/product_option_title')}` ( + `option_title_id` int(10) unsigned NOT NULL auto_increment, + `option_id` int(10) unsigned NOT NULL default '0', + `store_id` smallint(5) unsigned NOT NULL default '0', + `title` VARCHAR(255) NOT NULL default '', + PRIMARY KEY (`option_title_id`), + KEY `CATALOG_PRODUCT_OPTION_TITLE_OPTION` (`option_id`), + KEY `CATALOG_PRODUCT_OPTION_TITLE_STORE` (`store_id`), + KEY `IDX_CATALOG_PRODUCT_OPTION_TITLE_SI_OI` (`store_id`,`option_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_OPTION_TITLE_OPTION` FOREIGN KEY (`option_id`) REFERENCES `{$installer->getTable('catalog/product_option')}` (`option_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_OPTION_TITLE_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core/store')}` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +)ENGINE=InnoDB default CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('catalog/product_option_type_value')}`; +CREATE TABLE `{$installer->getTable('catalog/product_option_type_value')}` ( + `option_type_id` int(10) unsigned NOT NULL auto_increment, + `option_id` int(10) unsigned NOT NULL default '0', + `sku` varchar(64) NOT NULL default '', + `sort_order` int(10) unsigned NOT NULL default '0', + PRIMARY KEY (`option_type_id`), + KEY `CATALOG_PRODUCT_OPTION_TYPE_VALUE_OPTION` (`option_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_OPTION_TYPE_VALUE_OPTION` FOREIGN KEY (`option_id`) REFERENCES `{$installer->getTable('catalog/product_option')}` (`option_id`) ON DELETE CASCADE ON UPDATE CASCADE +)ENGINE=InnoDB default CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('catalog/product_option_type_price')}`; +CREATE TABLE `{$installer->getTable('catalog/product_option_type_price')}` ( + `option_type_price_id` int(10) unsigned NOT NULL auto_increment, + `option_type_id` int(10) unsigned NOT NULL default '0', + `store_id` smallint(5) unsigned NOT NULL default '0', + `price` decimal(12,4) NOT NULL default '0.00', + `price_type` enum('fixed','percent') NOT NULL default 'fixed', + PRIMARY KEY (`option_type_price_id`), + KEY `CATALOG_PRODUCT_OPTION_TYPE_PRICE_OPTION_TYPE` (`option_type_id`), + KEY `CATALOG_PRODUCT_OPTION_TYPE_PRICE_STORE` (`store_id`), + KEY `IDX_CATALOG_PRODUCT_OPTION_TYPE_PRICE_SI_OTI` (`store_id`,`option_type_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_OPTION_TYPE_PRICE_OPTION` FOREIGN KEY (`option_type_id`) REFERENCES `{$installer->getTable('catalog/product_option_type_value')}` (`option_type_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_OPTION_TYPE_PRICE_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core/store')}` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +)ENGINE=InnoDB default CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('catalog/product_option_type_title')}`; +CREATE TABLE `{$installer->getTable('catalog/product_option_type_title')}` ( + `option_type_title_id` int(10) unsigned NOT NULL auto_increment, + `option_type_id` int(10) unsigned NOT NULL default '0', + `store_id` smallint(5) unsigned NOT NULL default '0', + `title` varchar(255) NOT NULL default '', + PRIMARY KEY (`option_type_title_id`), + KEY `CATALOG_PRODUCT_OPTION_TYPE_TITLE_OPTION` (`option_type_id`), + KEY `CATALOG_PRODUCT_OPTION_TYPE_TITLE_STORE` (`store_id`), + KEY `IDX_CATALOG_PRODUCT_OPTION_TYPE_TITLE_SI_OTI` (`store_id`,`option_type_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_OPTION_TYPE_TITLE_OPTION` FOREIGN KEY (`option_type_id`) REFERENCES `{$installer->getTable('catalog/product_option_type_value')}` (`option_type_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_OPTION_TYPE_TITLE_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core/store')}` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +)ENGINE=InnoDB default CHARSET=utf8; + + +ALTER TABLE `{$installer->getTable('core_url_rewrite')}` ADD INDEX `IDX_CATEGORY_REWRITE` (`category_id`, `is_system`, `product_id`, `store_id`, `id_path`); +"); + + +$installer->run(" +CREATE TABLE `{$installer->getTable('catalog/eav_attribute')}` ( + `attribute_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, + `frontend_input_renderer` varchar(255) DEFAULT NULL, + `is_global` tinyint(1) unsigned NOT NULL DEFAULT '1', + `is_visible` tinyint(1) unsigned NOT NULL DEFAULT '1', + `is_searchable` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_filterable` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_comparable` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_visible_on_front` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_html_allowed_on_front` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_used_for_price_rules` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_filterable_in_search` tinyint(1) unsigned NOT NULL DEFAULT '0', + `used_in_product_listing` tinyint(1) unsigned NOT NULL DEFAULT '0', + `used_for_sort_by` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_configurable` tinyint(1) unsigned NOT NULL DEFAULT '1', + `apply_to` varchar(255) NOT NULL, + `is_visible_in_advanced_search` tinyint(1) unsigned NOT NULL DEFAULT '0', + `position` int(11) NOT NULL, + PRIMARY KEY (`attribute_id`), + KEY `IDX_USED_FOR_SORT_BY` (`used_for_sort_by`), + KEY `IDX_USED_IN_PRODUCT_LISTING` (`used_in_product_listing`), + CONSTRAINT `FK_CATALOG_EAV_ATTRIBUTE_ID` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav/attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +"); + +} + +$installer->endSetup(); + +$installer->installEntities(); + + + + +// Create Root Catalog Node +Mage::getModel('catalog/category') + ->setStoreId(0) + ->setId(1) + ->setPath(1) + ->setName('Root Catalog') + ->setInitialSetupFlag(true) + ->save(); + +/* @var $category Mage_Catalog_Model_Category */ +$category = Mage::getModel('catalog/category'); + +$category->setStoreId(0) + ->setName('Default Category') + ->setDisplayMode('PRODUCTS') + ->setAttributeSetId($category->getDefaultAttributeSetId()) + ->setIsActive(1) + ->setPath('1') + ->setInitialSetupFlag(true) + ->save(); + +$installer->setConfigData('catalog/category/root_id', $category->getId()); + +$installer->addAttributeGroup('catalog_product', 'Default', 'Design', 6); + +$entityTypeId = $installer->getEntityTypeId('catalog_category'); +$attributeSetId = $installer->getDefaultAttributeSetId($entityTypeId); +$attributeGroupId = $installer->getDefaultAttributeGroupId($entityTypeId, $attributeSetId); + +// update General Group +$installer->updateAttributeGroup($entityTypeId, $attributeSetId, $attributeGroupId, 'attribute_group_name', 'General Information'); +$installer->updateAttributeGroup($entityTypeId, $attributeSetId, $attributeGroupId, 'sort_order', '10'); + +$groups = array( + 'display' => array( + 'name' => 'Display Settings', + 'sort' => 20, + 'id' => null + ), + 'design' => array( + 'name' => 'Custom Design', + 'sort' => 30, + 'id' => null + ) +); + +foreach ($groups as $k => $groupProp) { + $installer->addAttributeGroup($entityTypeId, $attributeSetId, $groupProp['name'], $groupProp['sort']); + $groups[$k]['id'] = $installer->getAttributeGroupId($entityTypeId, $attributeSetId, $groupProp['name']); +} + +// update attributes group and sort +$attributes = array( + 'custom_design' => array( + 'group' => 'design', + 'sort' => 10 + ), + 'custom_design_apply' => array( + 'group' => 'design', + 'sort' => 20 + ), + 'custom_design_from' => array( + 'group' => 'design', + 'sort' => 30 + ), + 'custom_design_to' => array( + 'group' => 'design', + 'sort' => 40 + ), + 'page_layout' => array( + 'group' => 'design', + 'sort' => 50 + ), + 'custom_layout_update' => array( + 'group' => 'design', + 'sort' => 60 + ), + 'display_mode' => array( + 'group' => 'display', + 'sort' => 10 + ), + 'landing_page' => array( + 'group' => 'display', + 'sort' => 20 + ), + 'is_anchor' => array( + 'group' => 'display', + 'sort' => 30 + ), + 'available_sort_by' => array( + 'group' => 'display', + 'sort' => 40 + ), + 'default_sort_by' => array( + 'group' => 'display', + 'sort' => 50 + ), +); + +foreach ($attributes as $attributeCode => $attributeProp) { + $installer->addAttributeToGroup( + $entityTypeId, + $attributeSetId, + $groups[$attributeProp['group']]['id'], + $attributeCode, + $attributeProp['sort'] + ); +} + +$describe = $installer->getConnection()->describeTable($installer->getTable('catalog/eav_attribute')); +foreach ($describe as $columnData) { + if ($columnData['COLUMN_NAME'] == 'attribute_id') { + continue; + } + $installer->getConnection()->dropColumn($installer->getTable('eav/attribute'), $columnData['COLUMN_NAME']); +} diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.72-0.7.73.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.72-0.7.73.php new file mode 100644 index 0000000000..4fecd82d2e --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.72-0.7.73.php @@ -0,0 +1,50 @@ +startSetup(); +$installer->getConnection()->dropForeignKey($installer->getTable('catalog/product_super_attribute_label'), + 'FK_SUPER_PRODUCT_ATTRIBUTE_LABEL'); +$installer->getConnection()->dropForeignKey($installer->getTable('catalog/product_super_attribute_label'), + 'catalog_product_super_attribute_label_ibfk_1'); +$installer->getConnection()->dropKey($installer->getTable('catalog/product_super_attribute_label'), + 'IDX_CATALOG_PRODUCT_SUPER_ATTRIBUTE_STORE_PSAI_SI'); +$installer->getConnection()->addColumn($installer->getTable('catalog/product_super_attribute_label'), + 'use_default', 'tinyint(1) UNSIGNED DEFAULT 0 AFTER store_id'); +$installer->getConnection()->addConstraint('FK_CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL_ATTRIBUTE', + $installer->getTable('catalog/product_super_attribute_label'), 'product_super_attribute_id', + $installer->getTable('catalog/product_super_attribute'), 'product_super_attribute_id', + 'cascade', 'cascade', true); +$installer->getConnection()->addConstraint('FK_CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL_STORE', + $installer->getTable('catalog/product_super_attribute_label'), 'store_id', + $installer->getTable('core/store'), 'store_id', + 'cascade', 'cascade', true); +$installer->getConnection()->addKey($installer->getTable('catalog/product_super_attribute_label'), + 'UNQ_ATTRIBUTE_STORE', array('product_super_attribute_id', 'store_id'), 'unique'); +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.73-1.4.0.0.0.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.73-1.4.0.0.0.php new file mode 100644 index 0000000000..bc66e5c546 --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-0.7.73-1.4.0.0.0.php @@ -0,0 +1,95 @@ +startSetup(); +$installer->updateEntityType('catalog_category', 'additional_attribute_table', 'catalog/eav_attribute'); +$installer->updateEntityType('catalog_product', 'additional_attribute_table', 'catalog/eav_attribute'); +$installer->updateEntityType('catalog_category', 'entity_attribute_collection', 'catalog/attribute_collection'); +$installer->updateEntityType('catalog_product', 'entity_attribute_collection', 'catalog/attribute_collection'); +$installer->run(" +CREATE TABLE `{$installer->getTable('catalog/eav_attribute')}` ( + `attribute_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, + `frontend_input_renderer` varchar(255) DEFAULT NULL, + `is_global` tinyint(1) unsigned NOT NULL DEFAULT '1', + `is_visible` tinyint(1) unsigned NOT NULL DEFAULT '1', + `is_searchable` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_filterable` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_comparable` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_visible_on_front` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_html_allowed_on_front` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_used_for_price_rules` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_filterable_in_search` tinyint(1) unsigned NOT NULL DEFAULT '0', + `used_in_product_listing` tinyint(1) unsigned NOT NULL DEFAULT '0', + `used_for_sort_by` tinyint(1) unsigned NOT NULL DEFAULT '0', + `is_configurable` tinyint(1) unsigned NOT NULL DEFAULT '1', + `apply_to` varchar(255) NOT NULL, + `is_visible_in_advanced_search` tinyint(1) unsigned NOT NULL DEFAULT '0', + `position` int(11) NOT NULL, + PRIMARY KEY (`attribute_id`), + KEY `IDX_USED_FOR_SORT_BY` (`used_for_sort_by`), + KEY `IDX_USED_IN_PRODUCT_LISTING` (`used_in_product_listing`), + CONSTRAINT `FK_CATALOG_EAV_ATTRIBUTE_ID` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav/attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +"); + +$fields = array(); +$describe = $installer->getConnection()->describeTable($installer->getTable('catalog/eav_attribute')); +foreach ($describe as $columnData) { + $fields[] = $columnData['COLUMN_NAME']; +} +$stmt = $installer->getConnection()->select() + ->from($installer->getTable('eav/attribute'), $fields) + ->where('entity_type_id = ?', $installer->getEntityTypeId('catalog_category')) + ->orWhere('entity_type_id = ?', $installer->getEntityTypeId('catalog_product')); +$result = $installer->getConnection()->fetchAll($stmt); +$installer->getConnection()->insertMultiple($installer->getTable('catalog/eav_attribute'), $result); + +$describe = $installer->getConnection()->describeTable($installer->getTable('catalog/eav_attribute')); +foreach ($describe as $columnData) { + if ($columnData['COLUMN_NAME'] == 'attribute_id') { + continue; + } + $installer->getConnection()->dropColumn($installer->getTable('eav/attribute'), $columnData['COLUMN_NAME']); +} + +$prefix = Mage_Catalog_Model_Entity_Attribute::MODULE_NAME.Mage_Core_Model_Translate::SCOPE_SEPARATOR; +$sql = " + INSERT + INTO `{$installer->getTable('eav/attribute_label')}` (`attribute_id`, `store_id`, `value`) + SELECT + `attribute`.attribute_id, `translate`.store_id, `translate`.translate + FROM + `{$installer->getTable('eav/attribute')}` AS `attribute` + INNER JOIN `{$installer->getTable('core/translate')}` AS `translate` ON `translate`.string = CONCAT('{$prefix}', `attribute`.frontend_label) + WHERE + `translate`.store_id != 0 +"; +$installer->getConnection()->query($sql); +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.0-1.4.0.0.1.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.0-1.4.0.0.1.php new file mode 100644 index 0000000000..f6206ffa55 --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.0-1.4.0.0.1.php @@ -0,0 +1,38 @@ +startSetup(); +$installer->getConnection()->modifyColumn($installer->getTable('catalog/product') . '_int', + 'value', 'int(11) default NULL'); +$installer->getConnection()->modifyColumn($installer->getTable('catalog/product') . '_decimal', + 'value', 'decimal(12,4) default NULL'); +$installer->getConnection()->modifyColumn($installer->getTable('catalog/product') . '_datetime', + 'value', 'datetime default NULL'); +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.1-1.4.0.0.2.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.1-1.4.0.0.2.php new file mode 100644 index 0000000000..0feb751558 --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.1-1.4.0.0.2.php @@ -0,0 +1,38 @@ +startSetup(); +$installer->getConnection()->modifyColumn($installer->getTable('catalog/category') . '_int', + 'value', 'int(11) default NULL'); +$installer->getConnection()->modifyColumn($installer->getTable('catalog/category') . '_decimal', + 'value', 'decimal(12,4) default NULL'); +$installer->getConnection()->modifyColumn($installer->getTable('catalog/category') . '_datetime', + 'value', 'datetime default NULL'); +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.2-1.4.0.0.3.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.2-1.4.0.0.3.php new file mode 100644 index 0000000000..630ada05cf --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.2-1.4.0.0.3.php @@ -0,0 +1,50 @@ +startSetup(); + +$attributes = array( + $installer->getAttributeId('catalog_product', 'cost') +); + +$sql = $installer->getConnection()->quoteInto("SELECT * FROM `{$installer->getTable('catalog/eav_attribute')}` WHERE attribute_id IN (?)", $attributes); +$data = $installer->getConnection()->fetchAll($sql); + +foreach ($data as $row) { + $row['apply_to'] = array_flip(explode(',', $row['apply_to'])); + unset($row['apply_to']['configurable']); + $row['apply_to'] = implode(',', array_flip($row['apply_to'])); + + $installer->run("UPDATE `{$installer->getTable('catalog/eav_attribute')}` + SET `apply_to` = '{$row['apply_to']}' + WHERE `attribute_id` = {$row['attribute_id']}"); +} + +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.3-1.4.0.0.4.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.3-1.4.0.0.4.php new file mode 100644 index 0000000000..1bed6a8c7d --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.3-1.4.0.0.4.php @@ -0,0 +1,36 @@ +startSetup(); + +$installer->updateEntityType('catalog_category', 'entity_attribute_collection', 'catalog/category_attribute_collection'); +$installer->updateEntityType('catalog_product', 'entity_attribute_collection', 'catalog/product_attribute_collection'); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.4-1.4.0.0.5.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.4-1.4.0.0.5.php new file mode 100644 index 0000000000..1f3be0e6cb --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.4-1.4.0.0.5.php @@ -0,0 +1,73 @@ +startSetup(); +$groupedLinkType = Mage_Catalog_Model_Product_Link::LINK_TYPE_GROUPED; +$installer->run(" + +CREATE TABLE `{$installer->getTable('catalog/product_relation')}` ( + `parent_id` INT(10) UNSIGNED NOT NULL, + `child_id` INT(10) UNSIGNED NOT NULL, + PRIMARY KEY (`parent_id`,`child_id`), + KEY `IDX_CHILD` (`child_id`), + CONSTRAINT `FK_CATALOG_PRODUCT_RELATION_CHILD` FOREIGN KEY (`child_id`) REFERENCES `{$installer->getTable('catalog/product')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_RELATION_PARENT` FOREIGN KEY (`parent_id`) REFERENCES `{$installer->getTable('catalog/product')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=INNODB DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED; + +INSERT IGNORE INTO `{$installer->getTable('catalog/product_relation')}` +SELECT + `product_id`, + `linked_product_id` +FROM `{$installer->getTable('catalog/product_link')}` +WHERE `link_type_id`={$groupedLinkType}; + +INSERT IGNORE INTO `{$installer->getTable('catalog/product_relation')}` +SELECT + `parent_id`, + `product_id` +FROM `{$installer->getTable('catalog/product_super_link')}`; + +CREATE TABLE `{$installer->getTable('catalog/product_index_eav')}` ( + `entity_id` int(10) unsigned NOT NULL, + `attribute_id` smallint(5) unsigned NOT NULL, + `store_id` smallint(5) unsigned NOT NULL, + `value` int(10) unsigned NOT NULL, + PRIMARY KEY (`entity_id`,`attribute_id`,`store_id`,`value`), + KEY `IDX_ENTITY` (`entity_id`), + KEY `IDX_ATTRIBUTE` (`attribute_id`), + KEY `IDX_STORE` (`store_id`), + KEY `IDX_VALUE` (`value`), + CONSTRAINT `FK_CATALOG_PRODUCT_INDEX_EAV_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav/attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_INDEX_EAV_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES `{$installer->getTable('catalog/product')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_INDEX_EAV_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core/store')}` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +"); +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.5-1.4.0.0.6.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.5-1.4.0.0.6.php new file mode 100644 index 0000000000..cce1b89c1e --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.5-1.4.0.0.6.php @@ -0,0 +1,52 @@ +startSetup(); +$installer->run(" + +CREATE TABLE `{$installer->getTable('catalog/product_index_price')}` ( + `entity_id` INT(10) UNSIGNED NOT NULL, + `customer_group_id` SMALLINT(5) UNSIGNED NOT NULL, + `website_id` SMALLINT(5) UNSIGNED NOT NULL, + `tax_class_id` SMALLINT(5) UNSIGNED DEFAULT '0', + `price` DECIMAL(12,4) DEFAULT NULL, + `min_price` DECIMAL(12,4) DEFAULT NULL, + `max_price` DECIMAL(12,4) DEFAULT NULL, + PRIMARY KEY (`entity_id`,`customer_group_id`,`website_id`), + KEY `IDX_CUSTOMER_GROUP` (`customer_group_id`), + KEY `IDX_WEBSITE` (`website_id`), + KEY `IDX_MIN_PRICE` (`min_price`), + CONSTRAINT `FK_CATALOG_PRODUCT_INDEX_PRICE_WEBSITE` FOREIGN KEY (`website_id`) REFERENCES `{$installer->getTable('core/website')}` (`website_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_INDEX_PRICE_CUSTOMER_GROUP` FOREIGN KEY (`customer_group_id`) REFERENCES `{$installer->getTable('customer/customer_group')}` (`customer_group_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CATALOG_PRODUCT_INDEX_PRICE_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES `{$installer->getTable('catalog/product')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=INNODB DEFAULT CHARSET=utf8; + +"); +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.6-1.4.0.0.7.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.6-1.4.0.0.7.php new file mode 100644 index 0000000000..a8f608eaf0 --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.6-1.4.0.0.7.php @@ -0,0 +1,33 @@ +startSetup(); +$installer->updateAttribute($installer->getEntityTypeId('catalog_category'), 'is_active', 'is_required', true); +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.7-1.4.0.0.8.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.7-1.4.0.0.8.php new file mode 100644 index 0000000000..eef469fcae --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.7-1.4.0.0.8.php @@ -0,0 +1,34 @@ +updateAttribute('catalog_category', 'url_key', 'is_global', Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE); +$installer->updateAttribute('catalog_category', 'url_path', 'is_global', Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE); + +$installer->updateAttribute('catalog_product', 'url_key', 'is_global', Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE); +$installer->updateAttribute('catalog_product', 'url_path', 'is_global', Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE); diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.8-1.4.0.0.9.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.8-1.4.0.0.9.php new file mode 100644 index 0000000000..c822ac9df4 --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.8-1.4.0.0.9.php @@ -0,0 +1,36 @@ +startSetup(); +$installer->getConnection()->addColumn($installer->getTable('catalog/product_index_price'), 'final_price', + 'DECIMAL(12,4) DEFAULT NULL AFTER `price`'); +$installer->getConnection()->addColumn($installer->getTable('catalog/product_index_price'), 'tier_price', + 'DECIMAL(12,4) DEFAULT NULL'); +$installer->endSetup(); diff --git a/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.9-1.4.0.0.10.php b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.9-1.4.0.0.10.php new file mode 100644 index 0000000000..cb7ae2dca3 --- /dev/null +++ b/app/code/core/Mage/Catalog/sql/catalog_setup/mysql4-upgrade-1.4.0.0.9-1.4.0.0.10.php @@ -0,0 +1,51 @@ +getTable('catalog/category_product_index'); + +/** + * Remove data duplicates + */ +$stmt = $installer->getConnection()->query( + 'SELECT * FROM ' . $table . ' GROUP BY category_id, product_id, store_id HAVING count(*)>1' +); + +while ($row = $stmt->fetch()) { + $condition = 'category_id=' . $row['category_id'] + . ' AND product_id=' . $row['product_id'] + . ' AND store_id=' . $row['store_id'] . ' AND is_parent=0'; + $installer->getConnection()->delete($table, $condition); +} + +$installer->getConnection()->addKey( + $table, + 'UNQ_CATEGORY_PRODUCT', + array('category_id', 'product_id', 'store_id'), + 'unique' +); \ No newline at end of file diff --git a/app/code/core/Mage/CatalogIndex/Model/Indexer.php b/app/code/core/Mage/CatalogIndex/Model/Indexer.php index a5002b5840..89ebf29706 100644 --- a/app/code/core/Mage/CatalogIndex/Model/Indexer.php +++ b/app/code/core/Mage/CatalogIndex/Model/Indexer.php @@ -165,8 +165,9 @@ protected function _getWebsites() * @param mixed $product * @return Mage_CatalogIndex_Model_Indexer */ - public function cleanup($product) { - $this->_getResource()->clear(true, true, true, true, true, $product); + public function cleanup($product) + { + $this->_getResource()->clear(true, true, true, true, true, $product, ($product->getNeedStoreForReindex() === true ? $this->_getStores() : null)); return $this; } diff --git a/app/code/core/Mage/CatalogIndex/Model/Indexer/Eav.php b/app/code/core/Mage/CatalogIndex/Model/Indexer/Eav.php index 53fab5d712..3d895fcbdb 100644 --- a/app/code/core/Mage/CatalogIndex/Model/Indexer/Eav.php +++ b/app/code/core/Mage/CatalogIndex/Model/Indexer/Eav.php @@ -77,7 +77,7 @@ protected function _isAttributeIndexable(Mage_Eav_Model_Entity_Attribute_Abstrac protected function _getIndexableAttributeConditions() { - $conditions = "frontend_input IN ('select', 'multiselect') AND (is_filterable IN (1, 2) OR is_visible_in_advanced_search = 1)"; + $conditions = "main_table.frontend_input IN ('select', 'multiselect') AND (additional_table.is_filterable IN (1, 2) OR additional_table.is_visible_in_advanced_search = 1)"; return $conditions; $conditions = array(); diff --git a/app/code/core/Mage/CatalogIndex/Model/Mysql4/Data/Abstract.php b/app/code/core/Mage/CatalogIndex/Model/Mysql4/Data/Abstract.php index 4d8c47b524..034f90e928 100644 --- a/app/code/core/Mage/CatalogIndex/Model/Mysql4/Data/Abstract.php +++ b/app/code/core/Mage/CatalogIndex/Model/Mysql4/Data/Abstract.php @@ -84,7 +84,12 @@ public function getAttributeData($products, $attributes, $store) $tableName = "{$this->getTable('catalog/product')}_{$suffix}"; $condition = "product.entity_id = c.entity_id AND c.store_id = {$store} AND c.attribute_id = d.attribute_id"; $defaultCondition = "product.entity_id = d.entity_id AND d.store_id = 0"; - $fields = array('entity_id', 'type_id', 'attribute_id'=>'IFNULL(c.attribute_id, d.attribute_id)', 'value'=>'IFNULL(c.value, d.value)'); + $fields = array( + 'entity_id', + 'type_id', + 'attribute_id' => 'IF(c.value_id>0, c.attribute_id, d.attribute_id)', + 'value' => 'IF(c.value_id>0, c.value, d.value)' + ); $select = $this->_getReadAdapter()->select() ->from(array('product'=>$this->getTable('catalog/product')), $fields) @@ -274,7 +279,8 @@ protected function _addAttributeFilter(Varien_Db_Select $select, $attributeCode, sprintf('`%s`.`attribute_id`=`%s`.`attribute_id`', $tableGlobal, $tableStore), $adapter->quoteInto(sprintf('`%s`.`store_id`=?', $tableStore), $store) )); - $whereCond = sprintf('IFNULL(`%s`.`value`, `%s`.`value`) IN(?)', $tableStore, $tableGlobal); + $whereCond = sprintf('IF(`%s`.`value_id`>0, `%s`.`value`, `%s`.`value`) IN(?)', + $tableStore, $tableStore, $tableGlobal); $select ->join( diff --git a/app/code/core/Mage/CatalogIndex/Model/Mysql4/Indexer/Abstract.php b/app/code/core/Mage/CatalogIndex/Model/Mysql4/Indexer/Abstract.php index 34b82e8139..8417f3d291 100644 --- a/app/code/core/Mage/CatalogIndex/Model/Mysql4/Indexer/Abstract.php +++ b/app/code/core/Mage/CatalogIndex/Model/Mysql4/Indexer/Abstract.php @@ -85,7 +85,8 @@ public function loadAttributeCodesByCondition($conditions) { $table = $this->getTable('eav/attribute'); $select = $this->_getReadAdapter()->select(); - $select->from($table, 'attribute_id'); + $select->from(array('main_table' => $table), 'attribute_id') + ->join(array('additional_table' => $this->getTable('catalog/eav_attribute')), 'additional_table.attribute_id=main_table.attribute_id'); $select->distinct(true); if (is_array($conditions)) { @@ -111,7 +112,6 @@ public function loadAttributeCodesByCondition($conditions) } else { $select->where($conditions); } - return $this->_getReadAdapter()->fetchCol($select); } } \ No newline at end of file diff --git a/app/code/core/Mage/CatalogIndex/Model/Observer.php b/app/code/core/Mage/CatalogIndex/Model/Observer.php index bfb5881e31..d25bf41f53 100644 --- a/app/code/core/Mage/CatalogIndex/Model/Observer.php +++ b/app/code/core/Mage/CatalogIndex/Model/Observer.php @@ -164,6 +164,7 @@ public function processPriceRuleApplication(Varien_Event_Observer $observer) public function processAfterDeleteEvent(Varien_Event_Observer $observer) { $eventProduct = $observer->getEvent()->getProduct(); + $eventProduct->setNeedStoreForReindex(true); $this->_getIndexer()->cleanup($eventProduct); $parentProductIds = $eventProduct->getParentProductIds(); @@ -387,14 +388,14 @@ public function catalogProductFlatRebuild(Varien_Event_Observer $observer) * @param Varien_Event_Observer $observer * @return Mage_CatalogIndex_Model_Observer */ - public function catalogProductFlatUpdateProduct(Varien_Event_Observer $observer) + public function catalogProductFlatUpdateProduct(Varien_Event_Observer $observer) { $storeId = $observer->getEvent()->getStoreId(); $tableName = $observer->getEvent()->getTable(); $productIds = $observer->getEvent()->getProductIds(); $this->_getIndexer()->updateCatalogProductFlat($storeId, $productIds, $tableName); - - return $this; + + return $this; } } \ No newline at end of file diff --git a/app/code/core/Mage/CatalogIndex/etc/config.xml b/app/code/core/Mage/CatalogIndex/etc/config.xml index 3a1339b63c..2afb2b4021 100644 --- a/app/code/core/Mage/CatalogIndex/etc/config.xml +++ b/app/code/core/Mage/CatalogIndex/etc/config.xml @@ -42,28 +42,28 @@ - - - - - 10 - catalogindex/data_simple - - - 20 - catalogindex/data_virtual - - - 30 - catalogindex/data_configurable - - - 50 - catalogindex/data_grouped - - - - + + + + + + + + + + + + + + + + + + + + + + @@ -89,112 +89,94 @@ Mage_CatalogIndex Mage_CatalogIndex_Model_Mysql4_Setup - core_setup - - core_write - - - core_read - - - - - singleton - catalogindex/observer - processStoreAdd - - - - - - - singleton - catalogindex/observer - processAfterSaveEvent - - - - - - - singleton - catalogindex/observer - processAttributeChangeEvent - - - - - - - singleton - catalogindex/observer - processAfterDeleteEvent - - - - - - - singleton - catalogindex/observer - registerParentIds - - - - - - - singleton - catalogindex/observer - processPriceScopeChange - - - - - - - singleton - catalogindex/observer - processPriceRuleApplication - - - - - - - singleton - catalogindex/observer - catalogProductImportAfter - - - - - - - singleton - catalogindex/observer - cleanCache - - - - - - - singleton - catalogindex/observer - catalogCategorySaveAfter - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - singleton catalogindex/observer clearPriceAggregation @@ -204,7 +186,6 @@ - singleton catalogindex/observer clearPriceAggregation @@ -214,48 +195,43 @@ - singleton catalogindex/observer clearSearchLayerCache - - - - singleton - catalogindex/observer - catalogProductFlatPrepareColumns - - - - - - - singleton - catalogindex/observer - catalogProductFlatPrepareIndexes - - - - - - - singleton - catalogindex/observer - catalogProductFlatRebuild - - - - - - - singleton - catalogindex/observer - catalogProductFlatUpdateProduct - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -267,15 +243,14 @@ - - - - singleton - catalogindex/observer - processProductsWebsitesChange - - - + + + + + + + + diff --git a/app/code/core/Mage/CatalogInventory/Helper/Data.php b/app/code/core/Mage/CatalogInventory/Helper/Data.php index e138fc5935..92d1a40959 100644 --- a/app/code/core/Mage/CatalogInventory/Helper/Data.php +++ b/app/code/core/Mage/CatalogInventory/Helper/Data.php @@ -29,6 +29,8 @@ */ class Mage_CatalogInventory_Helper_Data extends Mage_Core_Helper_Abstract { + const XML_PATH_SHOW_OUT_OF_STOCK = 'cataloginventory/options/show_out_of_stock'; + /** * All product types registry in scope of quantity availability * @@ -94,4 +96,14 @@ public function getConfigItemOptions() 'manage_stock' ); } + + /** + * Display out of stock products option + * + * @return bool + */ + public function isShowOutOfStock() + { + return Mage::getStoreConfigFlag(self::XML_PATH_SHOW_OUT_OF_STOCK); + } } diff --git a/app/code/core/Mage/CatalogInventory/Model/Indexer/Stock.php b/app/code/core/Mage/CatalogInventory/Model/Indexer/Stock.php new file mode 100644 index 0000000000..6ef7845a5e --- /dev/null +++ b/app/code/core/Mage/CatalogInventory/Model/Indexer/Stock.php @@ -0,0 +1,325 @@ + + */ +class Mage_CatalogInventory_Model_Indexer_Stock extends Mage_Index_Model_Indexer_Abstract +{ + /** + * @var array + */ + protected $_matchedEntities = array( + Mage_CatalogInventory_Model_Stock_Item::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Catalog_Model_Product::ENTITY => array( + Mage_Index_Model_Event::TYPE_MASS_ACTION, + Mage_Index_Model_Event::TYPE_DELETE + ), + Mage_Core_Model_Store::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Core_Model_Store_Group::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Core_Model_Config_Data::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Catalog_Model_Convert_Adapter_Product::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ) + ); + + /** + * Related config settings + * + * @var array + */ + protected $_relatedConfigSettings = array( + Mage_CatalogInventory_Model_Stock_Item::XML_PATH_MANAGE_STOCK, + Mage_CatalogInventory_Helper_Data::XML_PATH_SHOW_OUT_OF_STOCK + ); + + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('cataloginventory/indexer_stock'); + } + + /** + * Retrieve resource instance wrapper + * + * @return Mage_CatalogInventory_Model_Mysql4_Indexer_Stock + */ + protected function _getResource() + { + return parent::_getResource(); + } + + /** + * Retrieve Indexer name + * + * @return string + */ + public function getName() + { + return Mage::helper('cataloginventory')->__('Stock status'); + } + + /** + * Retrieve Indexer description + * + * @return string + */ + public function getDescription() + { + return Mage::helper('cataloginventory')->__('Index product stock status'); + } + + /** + * Check if event can be matched by process. + * Overwrote for specific config save, store and store groups save matching + * + * @param Mage_Index_Model_Event $event + * @return bool + */ + public function matchEvent(Mage_Index_Model_Event $event) + { + $entity = $event->getEntity(); + if ($entity == Mage_Core_Model_Store::ENTITY) { + /* @var $store Mage_Core_Model_Store */ + $store = $event->getDataObject(); + if ($store->isObjectNew()) { + return true; + } + return false; + } else if ($entity == Mage_Core_Model_Store_Group::ENTITY) { + /* @var $storeGroup Mage_Core_Model_Store_Group */ + $storeGroup = $event->getDataObject(); + if ($storeGroup->dataHasChangedFor('website_id')) { + return true; + } + return false; + } else if ($entity == Mage_Core_Model_Config_Data::ENTITY) { + $configData = $event->getDataObject(); + $path = $configData->getPath(); + if (in_array($path, $this->_relatedConfigSettings)) { + return $configData->isValueChanged(); + } + return false; + } + return parent::matchEvent($event); + } + + /** + * Register data required by process in event object + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerEvent(Mage_Index_Model_Event $event) + { + switch ($event->getEntity()) { + case Mage_CatalogInventory_Model_Stock_Item::ENTITY: + $this->_registerCatalogInventoryStockItemEvent($event); + break; + + case Mage_Catalog_Model_Product::ENTITY: + $this->_registerCatalogProductEvent($event); + break; + + case Mage_Catalog_Model_Convert_Adapter_Product::ENTITY: + $event->addNewData('cataloginventory_stock_reindex_all', true); + break; + + case Mage_Core_Model_Store::ENTITY: + case Mage_Core_Model_Store_Group::ENTITY: + case Mage_Core_Model_Config_Data::ENTITY: + $event->addNewData('cataloginventory_stock_skip_call_event_handler', true); + $process = $event->getProcess(); + $process->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX); + + if ($event->getEntity() == Mage_Core_Model_Config_Data::ENTITY) { + $configData = $event->getDataObject(); + if ($configData->getPath() == Mage_CatalogInventory_Helper_Data::XML_PATH_SHOW_OUT_OF_STOCK) { + Mage::getSingleton('index/indexer')->getProcessByCode('catalog_product_price') + ->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX); + Mage::getSingleton('index/indexer')->getProcessByCode('catalog_product_attribute') + ->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX); + } + } + break; + } + } + + /** + * Register data required by catalog product processes in event object + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerCatalogProductEvent(Mage_Index_Model_Event $event) + { + switch ($event->getType()) { + case Mage_Index_Model_Event::TYPE_MASS_ACTION: + $this->_registerCatalogProductMassActionEvent($event); + break; + + case Mage_Index_Model_Event::TYPE_DELETE: + $this->_registerCatalogProductDeleteEvent($event); + break; + } + } + + /** + * Register data required by cataloginventory stock item processes in event object + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerCatalogInventoryStockItemEvent(Mage_Index_Model_Event $event) + { + switch ($event->getType()) { + case Mage_Index_Model_Event::TYPE_SAVE: + $this->_registerStockItemSaveEvent($event); + break; + } + } + + /** + * Register data required by stock item save process in event object + * + * @param Mage_Index_Model_Event $event + * @return Mage_CatalogInventory_Model_Indexer_Stock + */ + protected function _registerStockItemSaveEvent(Mage_Index_Model_Event $event) + { + /* @var $object Mage_CatalogInventory_Model_Stock_Item */ + $object = $event->getDataObject(); + +// $properties = array( +// 'manage_stock', +// 'use_config_manage_stock', +// 'is_in_stock' +// ); + +// $reindexStock = false; +// foreach ($properties as $property) { +// if ($event->dataHasChangedFor($property)) { +// $reindexStock = true; +// break; +// } +// } + + $event->addNewData('reindex_stock', 1); + $event->addNewData('product_id', $object->getProductId()); + +// if ($reindexStock && !Mage::helper('cataloginventory')->isShowOutOfStock()) { +// } + + return $this; + } + + /** + * Register data required by product delete process in event object + * + * @param Mage_Index_Model_Event $event + * @return Mage_CatalogInventory_Model_Indexer_Stock + */ + protected function _registerCatalogProductDeleteEvent(Mage_Index_Model_Event $event) + { + /* @var $product Mage_Catalog_Model_Product */ + $product = $event->getDataObject(); + + $parentIds = $this->_getResource()->getProductParentsByChild($product->getId()); + if ($parentIds) { + $event->addNewData('reindex_stock_parent_ids', $parentIds); + } + + return $this; + } + + /** + * Register data required by product mass action process in event object + * + * @param Mage_Index_Model_Event $event + * @return Mage_CatalogInventory_Model_Indexer_Stock + */ + protected function _registerCatalogProductMassActionEvent(Mage_Index_Model_Event $event) + { + /* @var $actionObject Varien_Object */ + $actionObject = $event->getDataObject(); + $attributes = array( + 'status' + ); + $reindexStock = false; + + // check if attributes changed + $attrData = $actionObject->getAttributesData(); + if (is_array($attrData)) { + foreach ($attributes as $attributeCode) { + if (array_key_exists($attributeCode, $attrData)) { + $reindexStock = true; + break; + } + } + } + + // check changed websites + if ($actionObject->getWebsiteIds()) { + $reindexStock = true; + } + + // register affected products + if ($reindexStock) { + $event->addNewData('reindex_stock_product_ids', $actionObject->getProductIds()); + } + + return $this; + } + + /** + * Process event + * + * @param Mage_Index_Model_Event $event + */ + protected function _processEvent(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + if (!empty($data['cataloginventory_stock_reindex_all'])) { + $this->reindexAll(); + } + if (empty($data['cataloginventory_stock_skip_call_event_handler'])) { + $this->callEventHandler($event); + } + } +} diff --git a/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock.php b/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock.php new file mode 100644 index 0000000000..ab44cdeff7 --- /dev/null +++ b/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock.php @@ -0,0 +1,373 @@ + + */ +class Mage_CatalogInventory_Model_Mysql4_Indexer_Stock + extends Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Abstract +{ + /** + * Stock Indexer models per product type + * Sorted by priority + * + * @var array + */ + protected $_indexers; + + /** + * Default Stock Indexer resource model name + * + * @var string + */ + protected $_defaultIndexer = 'cataloginventory/indexer_stock_default'; + + /** + * Initialize connection and define main table + * + */ + protected function _construct() + { + $this->_init('cataloginventory/stock_status', 'product_id'); + } + + /** + * Process stock item save action + * + * @param Mage_Index_Model_Event $event + * @return Mage_CatalogInventory_Model_Mysql4_Indexer_Stock + */ + public function cataloginventoryStockItemSave(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + if (empty($data['product_id'])) { + return $this; + } + $write = $this->_getWriteAdapter(); + $productId = $data['product_id']; + $this->cloneIndexTable(true); + + $parentIds = $this->getRelationsByChild($productId); + if ($parentIds) { + $processIds = array_merge($parentIds, array($productId)); + } else { + $processIds = array($productId); + } + $this->_copyRelationIndexData($processIds, array($productId)); + + // retrieve product types by processIds + $select = $write->select() + ->from($this->getTable('catalog/product'), array('entity_id', 'type_id')) + ->where('entity_id IN(?)', $processIds); + $pairs = $write->fetchPairs($select); + + $byType = array(); + foreach ($pairs as $productId => $typeId) { + $byType[$typeId][$productId] = $productId; + } + + $indexers = $this->_getTypeIndexers(); + foreach ($indexers as $indexer) { + if (isset($byType[$indexer->getTypeId()])) { + $indexer->reindexEntity($byType[$indexer->getTypeId()]); + } + } + + $this->_copyIndexDataToMainTable($processIds); + return $this; + } + + public function catalogProductDelete(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + if (empty($data['reindex_stock_parent_ids'])) { + return $this; + } + + $this->cloneIndexTable(true); + + $processIds = array_keys($data['reindex_stock_parent_ids']); + $parentIds = array(); + foreach ($data['reindex_stock_parent_ids'] as $parentId => $parentType) { + $parentIds[$parentType][$parentId] = $parentId; + } + + $this->_copyRelationIndexData($processIds); + foreach ($parentIds as $parentType => $entityIds) { + $this->_getIndexer($parentType)->reindexEntity($entityIds); + } + + $this->_copyIndexDataToMainTable($parentIds); + + return $this; + } + + /** + * Process product mass update action + * + * @param Mage_Index_Model_Event $event + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price + */ + public function catalogProductMassAction(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + if (empty($data['reindex_stock_product_ids'])) { + return $this; + } + + $processIds = $data['reindex_stock_product_ids']; + + $write = $this->_getWriteAdapter(); + $select = $write->select() + ->from($this->getTable('catalog/product'), 'COUNT(*)'); + $pCount = $write->fetchOne($select); + + // if affected more 30% of all products - run reindex all products + if ($pCount * 0.3 < count($processIds)) { + return $this->reindexAll(); + } + + // calculate relations + $select = $write->select() + ->from($this->getTable('catalog/product_relation'), 'COUNT(DISTINCT parent_id)') + ->where('child_id IN(?)', $processIds); + $aCount = $write->fetchOne($select); + $select = $write->select() + ->from($this->getTable('catalog/product_relation'), 'COUNT(DISTINCT child_id)') + ->where('parent_id IN(?)', $processIds); + $bCount = $write->fetchOne($select); + + // if affected with relations more 30% of all products - run reindex all products + if ($pCount * 0.3 < count($processIds) + $aCount + $bCount) { + return $this->reindexAll(); + } + + $this->cloneIndexTable(true); + + // retrieve products types + $select = $write->select() + ->from($this->getTable('catalog/product'), array('entity_id', 'type_id')) + ->where('entity_id IN(?)', $processIds); + $pairs = $write->fetchPairs($select); + $byType = array(); + foreach ($pairs as $productId => $productType) { + $byType[$productType][$productId] = $productId; + } + + $compositeIds = array(); + $notCompositeIds = array(); + + foreach ($byType as $productType => $entityIds) { + $indexer = $this->_getIndexer($productType); + if ($indexer->getIsComposite()) { + $compositeIds += $entityIds; + } else { + $notCompositeIds += $entityIds; + } + } + + if (!empty($notCompositeIds)) { + $select = $write->select() + ->from( + array('l' => $this->getTable('catalog/product_relation')), + 'parent_id') + ->join( + array('e' => $this->getTable('catalog/product')), + 'e.entity_id = l.parent_id', + array('type_id')) + ->where('l.child_id IN(?)', $notCompositeIds); + $pairs = $write->fetchPairs($select); + foreach ($pairs as $productId => $productType) { + if (!in_array($productId, $processIds)) { + $processIds[] = $productId; + $byType[$productType][$productId] = $productId; + $compositeIds[$productId] = $productId; + } + } + } + + if (!empty($compositeIds)) { + $this->_copyRelationIndexData($compositeIds, $notCompositeIds); + } + + $indexers = $this->_getTypeIndexers(); + foreach ($indexers as $indexer) { + if (!empty($byType[$indexer->getTypeId()])) { + $indexer->reindexEntity($byType[$indexer->getTypeId()]); + } + } + + $this->_copyIndexDataToMainTable($processIds); + + return $this; + } + + /** + * Rebuild all index data + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Eav + */ + public function reindexAll() + { + $this->cloneIndexTable(true); + + foreach ($this->_getTypeIndexers() as $indexer) { + $indexer->reindexAll(); + } + + $this->syncData(); + return $this; + } + + /** + * Retrieve Stock Indexer Models per Product Type + * + * @return array + */ + protected function _getTypeIndexers() + { + if (is_null($this->_indexers)) { + $this->_indexers = array(); + $types = Mage::getSingleton('catalog/product_type')->getTypesByPriority(); + foreach ($types as $typeId => $typeInfo) { + if (isset($typeInfo['stock_indexer'])) { + $modelName = $typeInfo['stock_indexer']; + } else { + $modelName = $this->_defaultIndexer; + } + $isComposite = !empty($typeInfo['composite']); + $indexer = Mage::getResourceModel($modelName) + ->setTypeId($typeId) + ->setIsComposite($isComposite); + + $this->_indexers[$typeId] = $indexer; + } + } + return $this->_indexers; + } + + /** + * Retrieve Stock indexer by Product Type + * + * @param string $productTypeId + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + protected function _getIndexer($productTypeId) + { + $types = $this->_getTypeIndexers(); + if (!isset($types[$productTypeId])) { + Mage::throwException(Mage::helper('catalog')->__('Unsupported product type "%s"', $productTypeId)); + } + return $types[$productTypeId]; + } + + /** + * Retrieve parent ids and types by child id + * + * Return array with key product_id and value as product type id + * + * @param int $childId + * @return array + */ + public function getProductParentsByChild($childId) + { + $write = $this->_getWriteAdapter(); + $select = $write->select() + ->from(array('l' => $this->getTable('catalog/product_relation')), array('parent_id')) + ->join( + array('e' => $this->getTable('catalog/product')), + 'l.parent_id=e.entity_id', + array('e.type_id')) + ->where('l.child_id=?', $childId); + return $write->fetchPairs($select); + } + + /** + * Copy relations product index from primary index to temporary index table by parent entity + * + * @param array|int $parentIds + * @package array|int $excludeIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price + */ + protected function _copyRelationIndexData($parentIds, $excludeIds = null) + { + $write = $this->_getWriteAdapter(); + $select = $write->select() + ->from($this->getTable('catalog/product_relation'), array('child_id')) + ->where('parent_id IN(?)', $parentIds); + if (!is_null($excludeIds)) { + $select->where('child_id NOT IN(?)', $excludeIds); + } + + $children = $write->fetchCol($select); + + if ($children) { + $select = $write->select() + ->from($this->getMainTable()) + ->where('product_id IN(?)', $children); + $query = $select->insertFromSelect($this->getIdxTable()); + $write->query($query); + } + + return $this; + } + + /** + * Copy data from temporary index table to main table by defined ids + * + * @param array $processIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price + */ + protected function _copyIndexDataToMainTable($processIds) + { + $write = $this->_getWriteAdapter(); + $write->beginTransaction(); + try { + // remove old index + $where = $write->quoteInto('product_id IN(?)', $processIds); + $write->delete($this->getMainTable(), $where); + + // remove additional data from index + $where = $write->quoteInto('product_id NOT IN(?)', $processIds); + $write->delete($this->getIdxTable(), $where); + + // insert new index + $this->insertFromTable($this->getIdxTable(), $this->getMainTable()); + + $this->commit(); + } catch (Exception $e) { + $this->rollBack(); + throw $e; + } + + return $this; + } +} diff --git a/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Configurable.php b/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Configurable.php new file mode 100644 index 0000000000..24dbfc854c --- /dev/null +++ b/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Configurable.php @@ -0,0 +1,120 @@ + + */ +class Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Configurable + extends Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Default +{ + /** + * Reindex all stock status data for configurable products + * + * @return Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Configurable + */ + public function reindexAll() + { + $this->_prepareIndexTable(); + return $this; + } + + /** + * Reindex stock data for defined configurable product ids + * + * @param int|array $entityIds + * @return Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Configurable + */ + public function reindexEntity($entityIds) + { + $this->_prepareIndexTable($entityIds); + return $this; + } + + /** + * Prepare stock status data in temporary index table + * + * @param int|array $entityIds the product limitation + * @return Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Configurable + */ + protected function _prepareIndexTable($entityIds = null) + { + $write = $this->_getWriteAdapter(); + + $select = $write->select() + ->from(array('e' => $this->getTable('catalog/product')), array('entity_id')); + $this->_addWebsiteJoinToSelect($select, true); + $this->_addProductWebsiteJoinToSelect($select, 'cw.website_id', 'e.entity_id'); + $select->columns('cw.website_id') + ->join( + array('cis' => $this->getTable('cataloginventory/stock')), + '', + array('stock_id')) + ->joinLeft( + array('cisi' => $this->getTable('cataloginventory/stock_item')), + 'cisi.stock_id = cis.stock_id AND cisi.product_id = e.entity_id', + array()) + ->joinLeft( + array('l' => $this->getTable('catalog/product_super_link')), + 'l.parent_id = e.entity_id', + array()) + ->joinLeft( + array('i' => $this->getIdxTable()), + 'i.product_id = l.product_id AND cw.website_id = i.website_id AND cis.stock_id = i.stock_id', + array()) + ->columns(array('qty' => new Zend_Db_Expr('0'))) + ->where('cw.website_id != 0') + ->where('e.type_id = ?', $this->getTypeId()) + ->group(array('e.entity_id', 'cw.website_id', 'cis.stock_id')); + + // add limitation of status + $condition = $write->quoteInto('=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); + $this->_addAttributeToSelect($select, 'status', 'e.entity_id', 'cs.store_id', $condition); + + if ($this->_isManageStock()) { + $statusExpr = new Zend_Db_Expr('IF(cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0,' + . ' 1, cisi.is_in_stock)'); + } else { + $statusExpr = new Zend_Db_Expr('IF(cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 1,' + . 'cisi.is_in_stock, 1)'); + } + + $select->columns(array('status' => new Zend_Db_Expr("LEAST(MAX(i.stock_status), {$statusExpr})"))); + + if (!is_null($entityIds)) { + $select->where('e.entity_id IN(?)', $entityIds); + } + + $query = $select->insertFromSelect($this->getIdxTable()); + $write->query($query); + + return $this; + } +} diff --git a/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Default.php b/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Default.php new file mode 100644 index 0000000000..24d507cbd4 --- /dev/null +++ b/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Default.php @@ -0,0 +1,192 @@ + + */ +class Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Default + extends Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Abstract + implements Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Interface +{ + /** + * Current Product Type Id + * + * @var string + */ + protected $_typeId; + + /** + * Product Type is composite flag + * + * @var bool + */ + protected $_isComposite = false; + + /** + * Initialize connection and define main table name + * + */ + protected function _construct() + { + $this->_init('cataloginventory/stock_status', 'product_id'); + } + + /** + * Reindex all stock status data for default logic product type + * + * @return Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Default + */ + public function reindexAll() + { + $this->_prepareIndexTable(); + return $this; + } + + /** + * Reindex stock data for defined product ids + * + * @param int|array $entityIds + * @return Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Default + */ + public function reindexEntity($entityIds) + { + $this->_prepareIndexTable($entityIds); + return $this; + } + + /** + * Set active Product Type Id + * + * @param string $typeId + * @return Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Default + */ + public function setTypeId($typeId) + { + $this->_typeId = $typeId; + return $this; + } + + /** + * Retrieve active Product Type Id + * + * @throws Mage_Core_Exception + * @return string + */ + public function getTypeId() + { + if (is_null($this->_typeId)) { + Mage::throwException(Mage::helper('cataloginventory')->__('Undefined product type')); + } + return $this->_typeId; + } + + /** + * Set Product Type Composite flag + * + * @param bool $flag + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default + */ + public function setIsComposite($flag) + { + $this->_isComposite = (bool)$flag; + return $this; + } + + /** + * Check product type is composite + * + * @return bool + */ + public function getIsComposite() + { + return $this->_isComposite; + } + + /** + * Retrieve is Global Manage Stock enabled + * + * @return bool + */ + protected function _isManageStock() + { + return Mage::getStoreConfigFlag(Mage_CatalogInventory_Model_Stock_Item::XML_PATH_MANAGE_STOCK); + } + + /** + * Prepare stock status data in temporary index table + * + * @param int|array $entityIds the product limitation + * @return Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Default + */ + protected function _prepareIndexTable($entityIds = null) + { + $write = $this->_getWriteAdapter(); + $select = $write->select() + ->from(array('e' => $this->getTable('catalog/product')), array('entity_id')); + $this->_addWebsiteJoinToSelect($select, true); + $this->_addProductWebsiteJoinToSelect($select, 'cw.website_id', 'e.entity_id'); + $select->columns('cw.website_id') + ->join( + array('cis' => $this->getTable('cataloginventory/stock')), + '', + array('stock_id')) + ->joinLeft( + array('cisi' => $this->getTable('cataloginventory/stock_item')), + 'cisi.stock_id = cis.stock_id AND cisi.product_id = e.entity_id', + array()) + ->columns(new Zend_Db_Expr('IF(cisi.qty > 0, cisi.qty, 0)')) + ->where('cw.website_id != 0') + ->where('e.type_id = ?', $this->getTypeId()); + + // add limitation of status + $condition = $write->quoteInto('=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); + $this->_addAttributeToSelect($select, 'status', 'e.entity_id', 'cs.store_id', $condition); + + if ($this->_isManageStock()) { + $statusExpr = new Zend_Db_Expr('IF(cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0,' + . ' 1, cisi.is_in_stock)'); + } else { + $statusExpr = new Zend_Db_Expr('IF(cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 1,' + . 'cisi.is_in_stock, 1)'); + } + + $select->columns(array('status' => $statusExpr)); + + if (!is_null($entityIds)) { + $select->where('e.entity_id IN(?)', $entityIds); + } + + $query = $select->insertFromSelect($this->getIdxTable()); + $write->query($query); + + return $this; + } +} diff --git a/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Grouped.php b/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Grouped.php new file mode 100644 index 0000000000..f207988a8c --- /dev/null +++ b/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Grouped.php @@ -0,0 +1,121 @@ + + */ +class Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Grouped + extends Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Default +{ + /** + * Reindex all stock status data for configurable products + * + * @return Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Grouped + */ + public function reindexAll() + { + $this->_prepareIndexTable(); + return $this; + } + + /** + * Reindex stock data for defined configurable product ids + * + * @param int|array $entityIds + * @return Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Grouped + */ + public function reindexEntity($entityIds) + { + $this->_prepareIndexTable($entityIds); + return $this; + } + + /** + * Prepare stock status data in temporary index table + * + * @param int|array $entityIds the product limitation + * @return Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Grouped + */ + protected function _prepareIndexTable($entityIds = null) + { + $write = $this->_getWriteAdapter(); + + $select = $write->select() + ->from(array('e' => $this->getTable('catalog/product')), array('entity_id')); + $this->_addWebsiteJoinToSelect($select, true); + $this->_addProductWebsiteJoinToSelect($select, 'cw.website_id', 'e.entity_id'); + $select->columns('cw.website_id') + ->join( + array('cis' => $this->getTable('cataloginventory/stock')), + '', + array('stock_id')) + ->joinLeft( + array('cisi' => $this->getTable('cataloginventory/stock_item')), + 'cisi.stock_id = cis.stock_id AND cisi.product_id = e.entity_id', + array()) + ->joinLeft( + array('l' => $this->getTable('catalog/product_link')), + 'l.product_id = e.entity_id', + array()) + ->joinLeft( + array('i' => $this->getIdxTable()), + 'i.product_id = l.linked_product_id AND cw.website_id = i.website_id AND cis.stock_id = i.stock_id', + array()) + ->columns(array('qty' => new Zend_Db_Expr('0'))) + ->where('cw.website_id != 0') + ->where('e.type_id = ?', $this->getTypeId()) + ->where('l.link_type_id=?', Mage_Catalog_Model_Product_Link::LINK_TYPE_GROUPED) + ->group(array('e.entity_id', 'cw.website_id', 'cis.stock_id')); + + // add limitation of status + $condition = $write->quoteInto('=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); + $this->_addAttributeToSelect($select, 'status', 'e.entity_id', 'cs.store_id', $condition); + + if ($this->_isManageStock()) { + $statusExpr = new Zend_Db_Expr('IF(cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0,' + . ' 1, cisi.is_in_stock)'); + } else { + $statusExpr = new Zend_Db_Expr('IF(cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 1,' + . 'cisi.is_in_stock, 1)'); + } + + $select->columns(array('status' => new Zend_Db_Expr("LEAST(MAX(i.stock_status), {$statusExpr})"))); + + if (!is_null($entityIds)) { + $select->where('e.entity_id IN(?)', $entityIds); + } + + $query = $select->insertFromSelect($this->getIdxTable()); + $write->query($query); + + return $this; + } +} diff --git a/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Interface.php b/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Interface.php new file mode 100644 index 0000000000..edb79f8ee1 --- /dev/null +++ b/app/code/core/Mage/CatalogInventory/Model/Mysql4/Indexer/Stock/Interface.php @@ -0,0 +1,67 @@ + + */ +interface Mage_CatalogInventory_Model_Mysql4_Indexer_Stock_Interface +{ + /** + * Reindex all stock status data + * + * @return Mage_CatalogIndex_Model_Mysql4_Indexer_Stock_Interface + */ + public function reindexAll(); + + /** + * Reindex stock status data for defined ids + * + * @param int|array $entityIds + * @return Mage_CatalogIndex_Model_Mysql4_Indexer_Stock_Interface + */ + public function reindexEntity($entityIds); + + /** + * Set Product Type Id for indexer + * + * @param string $typeId + * @return Mage_CatalogIndex_Model_Mysql4_Indexer_Stock_Interface + */ + public function setTypeId($typeId); + + /** + * Retrieve Product Type Id for indexer + * + * @throws Mage_Core_Exception + * @return string + */ + public function getTypeId(); +} diff --git a/app/code/core/Mage/CatalogInventory/Model/Mysql4/Stock/Status.php b/app/code/core/Mage/CatalogInventory/Model/Mysql4/Stock/Status.php index 88354274ac..a990852ae7 100644 --- a/app/code/core/Mage/CatalogInventory/Model/Mysql4/Stock/Status.php +++ b/app/code/core/Mage/CatalogInventory/Model/Mysql4/Stock/Status.php @@ -212,4 +212,24 @@ public function addStockStatusToSelect(Varien_Db_Select $select, Mage_Core_Model return $this; } + + /** + * Add stock status limitation to catalog product price index select object + * + * @param Varien_Db_Select $select + * @param string|Zend_Db_Expr $entityField + * @param string|Zend_Db_Expr $websiteField + * @return Mage_CatalogInventory_Model_Mysql4_Stock_Status + */ + public function prepareCatalogProductIndexSelect(Varien_Db_Select $select, $entityField, $websiteField) + { + $select->join( + array('ciss' => $this->getMainTable()), + "ciss.product_id = {$entityField} AND ciss.website_id = {$websiteField}", + array() + ); + $select->where('ciss.stock_status=?', Mage_CatalogInventory_Model_Stock_Status::STATUS_IN_STOCK); + + return $this; + } } diff --git a/app/code/core/Mage/CatalogInventory/Model/Observer.php b/app/code/core/Mage/CatalogInventory/Model/Observer.php index 5d726a9722..274189d76b 100644 --- a/app/code/core/Mage/CatalogInventory/Model/Observer.php +++ b/app/code/core/Mage/CatalogInventory/Model/Observer.php @@ -484,4 +484,22 @@ public function addStockStatusToPrepareIndexSelect(Varien_Event_Observer $observ return $this; } + + /** + * Add stock status limitation to catalog product price index select object + * + * @param Varien_Event_Observer $observer + * @return Mage_CatalogInventory_Model_Observer + */ + public function prepareCatalogProductIndexSelect(Varien_Event_Observer $observer) + { + $select = $observer->getEvent()->getSelect(); + $entity = $observer->getEvent()->getEntityField(); + $website = $observer->getEvent()->getWebsiteField(); + + Mage::getSingleton('cataloginventory/stock_status') + ->prepareCatalogProductIndexSelect($select, $entity, $website); + + return $this; + } } diff --git a/app/code/core/Mage/CatalogInventory/Model/Stock.php b/app/code/core/Mage/CatalogInventory/Model/Stock.php index 6b94098e69..d9f4fb568b 100644 --- a/app/code/core/Mage/CatalogInventory/Model/Stock.php +++ b/app/code/core/Mage/CatalogInventory/Model/Stock.php @@ -1,167 +1,167 @@ - - */ -class Mage_CatalogInventory_Model_Stock extends Mage_Core_Model_Abstract -{ - const BACKORDERS_NO = 0; - const BACKORDERS_YES_NONOTIFY = 1; - const BACKORDERS_YES_NOTIFY = 2; - - /* deprecated */ - const BACKORDERS_BELOW = 1; - const BACKORDERS_YES = 2; - - const STOCK_OUT_OF_STOCK = 0; - const STOCK_IN_STOCK = 1; - - const DEFAULT_STOCK_ID = 1; - - protected function _construct() - { - $this->_init('cataloginventory/stock'); - } - - /** - * Retrieve stock identifier - * - * @return int - */ - public function getId() - { - return self::DEFAULT_STOCK_ID; - } - - /** - * Add stock item objects to products - * - * @param collection $products - * @return Mage_CatalogInventory_Model_Stock - */ - public function addItemsToProducts($productCollection) - { - $items = $this->getItemCollection() - ->addProductsFilter($productCollection) - ->joinStockStatus($productCollection->getStoreId()) - ->load(); - $stockItems = array(); - foreach ($items as $item) { - $stockItems[$item->getProductId()] = $item; - } - foreach ($productCollection as $product) { - if (isset($stockItems[$product->getId()])) { - $stockItems[$product->getId()]->assignProduct($product); - } - } - return $this; - } - - /** - * Retrieve items collection object with stock filter - * - * @return unknown - */ - public function getItemCollection() - { - return Mage::getResourceModel('cataloginventory/stock_item_collection') - ->addStockFilter($this->getId()); - } - - /** - * Subtract ordered qty for product - * - * @param Varien_Object $item - * @return Mage_CatalogInventory_Model_Stock - */ - public function registerItemSale(Varien_Object $item) - { - if ($productId = $item->getProductId()) { - $stockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($productId); - if (Mage::helper('catalogInventory')->isQty($stockItem->getTypeId())) { - if ($item->getStoreId()) { - $stockItem->setStoreId($item->getStoreId()); - } - if ($stockItem->checkQty($item->getQtyOrdered()) || Mage::app()->getStore()->isAdmin()) { - $stockItem->subtractQty($item->getQtyOrdered()); - $stockItem->save(); - } - } - } - else { - Mage::throwException(Mage::helper('cataloginventory')->__('Can not specify product identifier for order item')); - } - return $this; - } - - /** - * Get back to stock (when order is canceled or whatever else) - * - * @param int $productId - * @param numeric $qty - * @return Mage_CatalogInventory_Model_Stock - */ - public function backItemQty($productId, $qty) - { - $stockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($productId); - if ($stockItem->getId() && Mage::helper('catalogInventory')->isQty($stockItem->getTypeId())) { - $stockItem->addQty($qty); - if ($stockItem->getCanBackInStock() && $stockItem->getQty() > $stockItem->getMinQty()) { - $stockItem->setIsInStock(true) - ->setStockStatusChangedAutomaticallyFlag(true); - } - $stockItem->save(); - } - return $this; - } - - /** - * Lock stock items for product ids array - * - * @param array $productIds - * @return Mage_CatalogInventory_Model_Stock - */ - public function lockProductItems($productIds) - { - $this->_getResource()->lockProductItems($this, $productIds); - return $this; - } - - /** - * Enter description here... - * - * @param Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Link_Product_Collection $collection - * @return Mage_CatalogInventory_Model_Stock $this - */ - public function addInStockFilterToCollection($collection) - { - $this->getResource()->setInStockFilterToCollection($collection); - return $this; - } -} + + */ +class Mage_CatalogInventory_Model_Stock extends Mage_Core_Model_Abstract +{ + const BACKORDERS_NO = 0; + const BACKORDERS_YES_NONOTIFY = 1; + const BACKORDERS_YES_NOTIFY = 2; + + /* deprecated */ + const BACKORDERS_BELOW = 1; + const BACKORDERS_YES = 2; + + const STOCK_OUT_OF_STOCK = 0; + const STOCK_IN_STOCK = 1; + + const DEFAULT_STOCK_ID = 1; + + protected function _construct() + { + $this->_init('cataloginventory/stock'); + } + + /** + * Retrieve stock identifier + * + * @return int + */ + public function getId() + { + return self::DEFAULT_STOCK_ID; + } + + /** + * Add stock item objects to products + * + * @param collection $products + * @return Mage_CatalogInventory_Model_Stock + */ + public function addItemsToProducts($productCollection) + { + $items = $this->getItemCollection() + ->addProductsFilter($productCollection) + ->joinStockStatus($productCollection->getStoreId()) + ->load(); + $stockItems = array(); + foreach ($items as $item) { + $stockItems[$item->getProductId()] = $item; + } + foreach ($productCollection as $product) { + if (isset($stockItems[$product->getId()])) { + $stockItems[$product->getId()]->assignProduct($product); + } + } + return $this; + } + + /** + * Retrieve items collection object with stock filter + * + * @return unknown + */ + public function getItemCollection() + { + return Mage::getResourceModel('cataloginventory/stock_item_collection') + ->addStockFilter($this->getId()); + } + + /** + * Subtract ordered qty for product + * + * @param Varien_Object $item + * @return Mage_CatalogInventory_Model_Stock + */ + public function registerItemSale(Varien_Object $item) + { + if ($productId = $item->getProductId()) { + $stockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($productId); + if (Mage::helper('catalogInventory')->isQty($stockItem->getTypeId())) { + if ($item->getStoreId()) { + $stockItem->setStoreId($item->getStoreId()); + } + if ($stockItem->checkQty($item->getQtyOrdered()) || Mage::app()->getStore()->isAdmin()) { + $stockItem->subtractQty($item->getQtyOrdered()); + $stockItem->save(); + } + } + } + else { + Mage::throwException(Mage::helper('cataloginventory')->__('Can not specify product identifier for order item')); + } + return $this; + } + + /** + * Get back to stock (when order is canceled or whatever else) + * + * @param int $productId + * @param numeric $qty + * @return Mage_CatalogInventory_Model_Stock + */ + public function backItemQty($productId, $qty) + { + $stockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($productId); + if ($stockItem->getId() && Mage::helper('catalogInventory')->isQty($stockItem->getTypeId())) { + $stockItem->addQty($qty); + if ($stockItem->getCanBackInStock() && $stockItem->getQty() > $stockItem->getMinQty()) { + $stockItem->setIsInStock(true) + ->setStockStatusChangedAutomaticallyFlag(true); + } + $stockItem->save(); + } + return $this; + } + + /** + * Lock stock items for product ids array + * + * @param array $productIds + * @return Mage_CatalogInventory_Model_Stock + */ + public function lockProductItems($productIds) + { + $this->_getResource()->lockProductItems($this, $productIds); + return $this; + } + + /** + * Enter description here... + * + * @param Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Link_Product_Collection $collection + * @return Mage_CatalogInventory_Model_Stock $this + */ + public function addInStockFilterToCollection($collection) + { + $this->getResource()->setInStockFilterToCollection($collection); + return $this; + } +} diff --git a/app/code/core/Mage/CatalogInventory/Model/Stock/Item.php b/app/code/core/Mage/CatalogInventory/Model/Stock/Item.php index 13f5051863..132fb725f2 100644 --- a/app/code/core/Mage/CatalogInventory/Model/Stock/Item.php +++ b/app/code/core/Mage/CatalogInventory/Model/Stock/Item.php @@ -46,7 +46,23 @@ class Mage_CatalogInventory_Model_Stock_Item extends Mage_Core_Model_Abstract const XML_PATH_NOTIFY_STOCK_QTY = 'cataloginventory/item_options/notify_stock_qty'; const XML_PATH_MANAGE_STOCK = 'cataloginventory/item_options/manage_stock'; - // cataloginventory/cart_options/... + const ENTITY = 'cataloginventory_stock_item'; + + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'cataloginventory_stock_item'; + + /** + * Parameter name in event + * + * In observe method you can use $observer->getEvent()->getItem() in this case + * + * @var string + */ + protected $_eventObject = 'item'; /** * Initialize resource model @@ -298,6 +314,7 @@ public function checkQty($qty) */ public function checkQuoteItemQty($qty, $summaryQty, $origQty = 0) { + $result = new Varien_Object(); $result->setHasError(false); @@ -454,7 +471,6 @@ protected function _beforeSave() $this->setQty(0); } - Mage::dispatchEvent('cataloginventory_stock_item_save_before', array('item' => $this)); return $this; } @@ -466,8 +482,22 @@ protected function _beforeSave() protected function _afterSave() { parent::_afterSave(); - Mage::getSingleton('cataloginventory/stock_status') - ->changeItemStatus($this); +// Mage::getSingleton('cataloginventory/stock_status') +// ->changeItemStatus($this); + return $this; + } + + /** + * Init indexing process after stock item data commit + * + * @return Mage_CatalogInventory_Model_Stock_Item + */ + protected function _afterSaveCommit() + { + parent::_afterSaveCommit(); + Mage::getSingleton('index/indexer')->processEntityAction( + $this, self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE + ); return $this; } diff --git a/app/code/core/Mage/CatalogInventory/Model/Stock/Status.php b/app/code/core/Mage/CatalogInventory/Model/Stock/Status.php index 4f5d632538..ef0c65fb74 100644 --- a/app/code/core/Mage/CatalogInventory/Model/Stock/Status.php +++ b/app/code/core/Mage/CatalogInventory/Model/Stock/Status.php @@ -497,4 +497,23 @@ public function addStockStatusToSelect(Varien_Db_Select $select, Mage_Core_Model $this->_getResource()->addStockStatusToSelect($select, $website); return $this; } + + /** + * Add stock status limitation to catalog product price index select object + * + * @param Varien_Db_Select $select + * @param string|Zend_Db_Expr $entityField + * @param string|Zend_Db_Expr $websiteField + * @return Mage_CatalogInventory_Model_Stock_Status + */ + public function prepareCatalogProductIndexSelect(Varien_Db_Select $select, $entityField, $websiteField) + { + if (Mage::helper('cataloginventory')->isShowOutOfStock()) { + return $this; + } + + $this->_getResource()->prepareCatalogProductIndexSelect($select, $entityField, $websiteField); + + return $this; + } } diff --git a/app/code/core/Mage/CatalogInventory/etc/adminhtml.xml b/app/code/core/Mage/CatalogInventory/etc/adminhtml.xml new file mode 100644 index 0000000000..aaa531556c --- /dev/null +++ b/app/code/core/Mage/CatalogInventory/etc/adminhtml.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + Inventory Section + + + + + + + + + + diff --git a/app/code/core/Mage/CatalogInventory/etc/config.xml b/app/code/core/Mage/CatalogInventory/etc/config.xml index efb8226b2a..12a82ad350 100644 --- a/app/code/core/Mage/CatalogInventory/etc/config.xml +++ b/app/code/core/Mage/CatalogInventory/etc/config.xml @@ -63,27 +63,13 @@ Mage_CatalogInventory Mage_Eav_Model_Entity_Setup - - core_setup - - - - core_write - - - - - core_read - - - singleton cataloginventory/observer addInventoryData @@ -92,7 +78,6 @@ - singleton cataloginventory/observer addStockStatusToCollection @@ -101,7 +86,6 @@ - singleton cataloginventory/observer addStockStatusToPrepareIndexSelect @@ -110,25 +94,22 @@ - singleton cataloginventory/observer addInventoryDataToCollection - + - singleton cataloginventory/observer checkQuoteItemQty @@ -137,7 +118,6 @@ - singleton cataloginventory/observer lockOrderInventoryData @@ -146,7 +126,6 @@ - singleton cataloginventory/observer createOrderItem @@ -155,7 +134,6 @@ - singleton cataloginventory/observer cancelOrderItem @@ -164,7 +142,6 @@ - singleton cataloginventory/observer refundOrderItem @@ -173,7 +150,6 @@ - singleton cataloginventory/observer saveInventoryData @@ -182,7 +158,6 @@ - singleton cataloginventory/observer copyInventoryData @@ -191,21 +166,27 @@ - singleton cataloginventory/observer updateItemsStockUponConfigChange - + + + + + cataloginventory/observer + prepareCatalogProductIndexSelect + + + @@ -217,9 +198,23 @@ 1 + + cataloginventory/indexer_stock_configurable + + + cataloginventory/indexer_stock_grouped + + + + + + cataloginventory/indexer_stock + + + @@ -244,25 +239,6 @@ - - - - - - - - - - Inventory Section - - - - - - - - - @@ -270,6 +246,7 @@ 1 1 + 0 1 diff --git a/app/code/core/Mage/CatalogInventory/etc/system.xml b/app/code/core/Mage/CatalogInventory/etc/system.xml index 0d12c7fdca..026538adef 100644 --- a/app/code/core/Mage/CatalogInventory/etc/system.xml +++ b/app/code/core/Mage/CatalogInventory/etc/system.xml @@ -62,6 +62,15 @@ 1 1 + + Display out of stock products + select + adminhtml/system_config_source_yesno + 3 + 1 + 0 + 0 + diff --git a/app/code/core/Mage/CatalogRule/Model/Mysql4/Rule.php b/app/code/core/Mage/CatalogRule/Model/Mysql4/Rule.php index 905571e42b..77a2cb2be5 100644 --- a/app/code/core/Mage/CatalogRule/Model/Mysql4/Rule.php +++ b/app/code/core/Mage/CatalogRule/Model/Mysql4/Rule.php @@ -91,8 +91,9 @@ public function updateRuleProductData(Mage_CatalogRule_Model_Rule $rule) if (empty($websiteIds)) { return $this; } - + Varien_Profiler::start('__MATCH_PRODUCTS__'); $productIds = $rule->getMatchingProductIds(); + Varien_Profiler::stop('__MATCH_PRODUCTS__'); $customerGroupIds = $rule->getCustomerGroupIds(); $fromTime = strtotime($rule->getFromDate()); @@ -105,36 +106,31 @@ public function updateRuleProductData(Mage_CatalogRule_Model_Rule $rule) $actionStop = $rule->getStopRulesProcessing(); $rows = array(); - $header = 'replace into '.$this->getTable('catalogrule/rule_product').' ( - rule_id, - from_time, - to_time, - website_id, - customer_group_id, - product_id, - action_operator, - action_amount, - action_stop, - sort_order - ) values '; - + $queryStart = 'INSERT INTO '.$this->getTable('catalogrule/rule_product').' ( + rule_id, from_time, to_time, website_id, customer_group_id, product_id, action_operator, + action_amount, action_stop, sort_order ) values '; + $queryEnd = ' ON DUPLICATE KEY UPDATE action_operator=VALUES(action_operator), + action_amount=VALUES(action_amount), action_stop=VALUES(action_stop)'; try { foreach ($productIds as $productId) { foreach ($websiteIds as $websiteId) { foreach ($customerGroupIds as $customerGroupId) { - $rows[] = "( - '$ruleId', - '$fromTime', - '$toTime', - '$websiteId', - '$customerGroupId', - '$productId', - '$actionOperator', - '$actionAmount', - '$actionStop', - '$sortOrder')"; - if (sizeof($rows)==100) { - $sql = $header.join(',', $rows); + $rows[] = "('" . implode("','", array( + $ruleId, + $fromTime, + $toTime, + $websiteId, + $customerGroupId, + $productId, + $actionOperator, + $actionAmount, + $actionStop, + $sortOrder))."')"; + /** + * Array with 1000 rows contain about 2M data + */ + if (sizeof($rows)==1000) { + $sql = $queryStart.join(',', $rows).$queryEnd; $write->query($sql); $rows = array(); } @@ -142,7 +138,7 @@ public function updateRuleProductData(Mage_CatalogRule_Model_Rule $rule) } } if (!empty($rows)) { - $sql = $header.join(',', $rows); + $sql = $queryStart.join(',', $rows).$queryEnd; $write->query($sql); } @@ -434,156 +430,87 @@ public function applyAllRulesForDateRange($fromDate=null, $toDate=null, $product } try { - /** - * Update products rules prices per each website separatly - * because of max join limit in mysql - */ - foreach (Mage::app()->getWebsites(false) as $website) { - $productsStmt = $this->_getRuleProductsStmt( - $fromDate, - $toDate, - $productId, - $website->getId() - ); + /** + * Update products rules prices per each website separatly + * because of max join limit in mysql + */ + foreach (Mage::app()->getWebsites(false) as $website) { + $productsStmt = $this->_getRuleProductsStmt( + $fromDate, + $toDate, + $productId, + $website->getId() + ); - $dayPrices = array(); - $stopFlags = array(); - $prevKey = null; - - while ($ruleData = $productsStmt->fetch()) { - $ruleProductId = $ruleData['product_id']; - $productKey= $ruleProductId . '_' - . $ruleData['website_id'] . '_' - . $ruleData['customer_group_id']; - - if ($prevKey && ($prevKey != $productKey)) { - $stopFlags = array(); - } - - /** - * Build prices for each day - */ - for ($time=$fromDate; $time<=$toDate; $time+=self::SECONDS_IN_DAY) { - if (($ruleData['from_time']==0 || $time >= $ruleData['from_time']) - && ($ruleData['to_time']==0 || $time <=$ruleData['to_time'])) { - - $priceKey = $time . '_' . $productKey; - - if (isset($stopFlags[$priceKey])) { - continue; - } - - if (!isset($dayPrices[$priceKey])) { - $dayPrices[$priceKey] = array( - 'rule_date' => $time, - 'website_id' => $ruleData['website_id'], - 'customer_group_id' => $ruleData['customer_group_id'], - 'product_id' => $ruleProductId, - 'rule_price' => $this->_calcRuleProductPrice($ruleData), - 'latest_start_date' => $ruleData['from_time'], - 'earliest_end_date' => $ruleData['to_time'], - ); - } - else { - $dayPrices[$priceKey]['rule_price'] = $this->_calcRuleProductPrice( - $ruleData, - $dayPrices[$priceKey] - ); - $dayPrices[$priceKey]['latest_start_date'] = max( - $dayPrices[$priceKey]['latest_start_date'], - $ruleData['from_time'] - ); - $dayPrices[$priceKey]['earliest_end_date'] = min( - $dayPrices[$priceKey]['earliest_end_date'], - $ruleData['to_time'] - ); - } - - if ($ruleData['action_stop']) { - $stopFlags[$priceKey] = true; - } - } - } - - $prevKey = $productKey; - - if (count($dayPrices)>100) { - $this->_saveRuleProductPrices($dayPrices); - $dayPrices = array(); - } - } - $this->_saveRuleProductPrices($dayPrices); - } - $this->_saveRuleProductPrices($dayPrices); - $write->commit(); - - // -// $dayPrices = array(); -// $stopFlags = array(); -// $prevKey = null; -// while ($ruleData = $productsStmt->fetch()) { -// $productId = $ruleData['product_id']; -// $productKey= $productId . '_' . $ruleData['website_id'] . '_' . $ruleData['customer_group_id']; -// -// if ($prevKey && ($prevKey != $productKey)) { -// $stopFlags = array(); -// } -// -// /** -// * Build prices for each day -// */ -// for ($time=$fromDate; $time<=$toDate; $time+=self::SECONDS_IN_DAY) { -// -// if (($ruleData['from_time']==0 || $time >= $ruleData['from_time']) -// && ($ruleData['to_time']==0 || $time <=$ruleData['to_time'])) { -// -// $priceKey = $time . '_' . $productKey; -// -// if (isset($stopFlags[$priceKey])) { -// continue; -// } -// -// if (!isset($dayPrices[$priceKey])) { -// $dayPrices[$priceKey] = array( -// 'rule_date' => $time, -// 'website_id' => $ruleData['website_id'], -// 'customer_group_id' => $ruleData['customer_group_id'], -// 'product_id' => $productId, -// 'rule_price' => $this->_calcRuleProductPrice($ruleData), -// 'latest_start_date' => $ruleData['from_time'], -// 'earliest_end_date' => $ruleData['to_time'], -// ); -// } -// else { -// $dayPrices[$priceKey]['rule_price'] = $this->_calcRuleProductPrice( -// $ruleData, -// $dayPrices[$priceKey] -// ); -// $dayPrices[$priceKey]['latest_start_date'] = max( -// $dayPrices[$priceKey]['latest_start_date'], -// $ruleData['from_time'] -// ); -// $dayPrices[$priceKey]['earliest_end_date'] = min( -// $dayPrices[$priceKey]['earliest_end_date'], -// $ruleData['to_time'] -// ); -// } -// -// if ($ruleData['action_stop']) { -// $stopFlags[$priceKey] = true; -// } -// } -// } -// -// $prevKey = $productKey; -// -// if (count($dayPrices)>100) { -// $this->_saveRuleProductPrices($dayPrices); -// $dayPrices = array(); -// } -// } -// $this->_saveRuleProductPrices($dayPrices); -// $write->commit(); + $dayPrices = array(); + $stopFlags = array(); + $prevKey = null; + + while ($ruleData = $productsStmt->fetch()) { + $ruleProductId = $ruleData['product_id']; + $productKey= $ruleProductId . '_' + . $ruleData['website_id'] . '_' + . $ruleData['customer_group_id']; + + if ($prevKey && ($prevKey != $productKey)) { + $stopFlags = array(); + } + + /** + * Build prices for each day + */ + for ($time=$fromDate; $time<=$toDate; $time+=self::SECONDS_IN_DAY) { + if (($ruleData['from_time']==0 || $time >= $ruleData['from_time']) + && ($ruleData['to_time']==0 || $time <=$ruleData['to_time'])) { + + $priceKey = $time . '_' . $productKey; + + if (isset($stopFlags[$priceKey])) { + continue; + } + + if (!isset($dayPrices[$priceKey])) { + $dayPrices[$priceKey] = array( + 'rule_date' => $time, + 'website_id' => $ruleData['website_id'], + 'customer_group_id' => $ruleData['customer_group_id'], + 'product_id' => $ruleProductId, + 'rule_price' => $this->_calcRuleProductPrice($ruleData), + 'latest_start_date' => $ruleData['from_time'], + 'earliest_end_date' => $ruleData['to_time'], + ); + } + else { + $dayPrices[$priceKey]['rule_price'] = $this->_calcRuleProductPrice( + $ruleData, + $dayPrices[$priceKey] + ); + $dayPrices[$priceKey]['latest_start_date'] = max( + $dayPrices[$priceKey]['latest_start_date'], + $ruleData['from_time'] + ); + $dayPrices[$priceKey]['earliest_end_date'] = min( + $dayPrices[$priceKey]['earliest_end_date'], + $ruleData['to_time'] + ); + } + + if ($ruleData['action_stop']) { + $stopFlags[$priceKey] = true; + } + } + } + + $prevKey = $productKey; + if (count($dayPrices)>1000) { + $this->_saveRuleProductPrices($dayPrices); + $dayPrices = array(); + } + } + $this->_saveRuleProductPrices($dayPrices); + } + $this->_saveRuleProductPrices($dayPrices); + $write->commit(); } catch (Exception $e) { $write->rollback(); throw $e; @@ -655,13 +582,7 @@ protected function _saveRuleProductPrices($arrData) return $this; } $header = 'replace into '.$this->getTable('catalogrule/rule_product_price').' ( - rule_date, - website_id, - customer_group_id, - product_id, - rule_price, - latest_start_date, - earliest_end_date + rule_date, website_id, customer_group_id, product_id, rule_price, latest_start_date, earliest_end_date ) values '; $rows = array(); $productIds = array(); diff --git a/app/code/core/Mage/CatalogRule/Model/Mysql4/Rule/Product/Price.php b/app/code/core/Mage/CatalogRule/Model/Mysql4/Rule/Product/Price.php index 8cb6583925..a72f1d7597 100644 --- a/app/code/core/Mage/CatalogRule/Model/Mysql4/Rule/Product/Price.php +++ b/app/code/core/Mage/CatalogRule/Model/Mysql4/Rule/Product/Price.php @@ -25,10 +25,73 @@ */ +/** + * Catalog Rule Product Aggregated Price per date Resource Model + * + * @category Mage + * @package Mage_CatalogRule + * @author Magento Core Team + */ class Mage_CatalogRule_Model_Mysql4_Rule_Product_Price extends Mage_Core_Model_Mysql4_Abstract { + /** + * Initialize connection and define main table + * + */ protected function _construct() { $this->_init('catalogrule/rule_product_price', 'rule_product_price_id'); } -} \ No newline at end of file + + /** + * Apply price rule price to price index table + * + * @param Varien_Db_Select $select + * @param array|string $indexTable + * @param string $entityId + * @param string $customerGroupId + * @param string $websiteId + * @param array $updateFields the array of fields for compare with rule price and update + * @param string $websiteDate + * @return Mage_CatalogRule_Model_Rule_Product_Price + */ + public function applyPriceRuleToIndexTable(Varien_Db_Select $select, $indexTable, $entityId, $customerGroupId, + $websiteId, $updateFields, $websiteDate) + { + if (empty($updateFields)) { + return $this; + } + + if (is_array($indexTable)) { + foreach ($indexTable as $k => $v) { + if (is_string($k)) { + $indexAlias = $k; + } else { + $indexAlias = $v; + } + break; + } + } else { + $indexAlias = $indexTable; + } + + // join rule to select + $select->join( + array('rp' => $this->getMainTable()), + "rp.product_id = {$entityId} AND rp.website_id = {$websiteId}" + . " AND rp.customer_group_id = {$customerGroupId}" + . " AND rp.rule_date = {$websiteDate}", + array()); + + foreach ($updateFields as $priceField) { + $priceCond = $this->_getWriteAdapter()->quoteIdentifier(array($indexAlias, $priceField)); + $priceExpr = new Zend_Db_Expr("IF(rp.rule_price < {$priceCond}, rp.rule_price, {$priceCond})"); + $select->columns(array($priceField => $priceExpr)); + } + + $query = $select->crossUpdateFromSelect($indexTable); + $this->_getWriteAdapter()->query($query); + + return $this; + } +} diff --git a/app/code/core/Mage/CatalogRule/Model/Observer.php b/app/code/core/Mage/CatalogRule/Model/Observer.php index d93740c0a4..89612d5d1f 100644 --- a/app/code/core/Mage/CatalogRule/Model/Observer.php +++ b/app/code/core/Mage/CatalogRule/Model/Observer.php @@ -62,7 +62,8 @@ public function applyAllRulesOnProduct($observer) } /** - * Apply all price rules for current date + * Apply all price rules for current date. + * Handle cataolg_product_import_after event * * @param Varien_Event_Observer $observer * @return Mage_CatalogRule_Model_Observer @@ -176,4 +177,28 @@ public function flushPriceCache() { $this->_rulePrices = array(); } + + /** + * Calculate minimal final price with catalog rule price + * + * @param Varien_Event_Observer $observer + * @return Mage_CatalogRule_Model_Observer + */ + public function prepareCatalogProductPriceIndexTable(Varien_Event_Observer $observer) + { + $select = $observer->getEvent()->getSelect(); + $indexTable = $observer->getEvent()->getIndexTable(); + $entityId = $observer->getEvent()->getEntityId(); + $customerGroupId = $observer->getEvent()->getCustomerGroupId(); + $websiteId = $observer->getEvent()->getWebsiteId(); + + $websiteDate = $observer->getEvent()->getWebsiteDate(); + $updateFields = $observer->getEvent()->getUpdateFields(); + + Mage::getSingleton('catalogrule/rule_product_price') + ->applyPriceRuleToIndexTable($select, $indexTable, $entityId, $customerGroupId, $websiteId, + $updateFields, $websiteDate); + + return $this; + } } \ No newline at end of file diff --git a/app/code/core/Mage/CatalogRule/Model/Rule.php b/app/code/core/Mage/CatalogRule/Model/Rule.php index 3c13a6aa8a..23570da443 100644 --- a/app/code/core/Mage/CatalogRule/Model/Rule.php +++ b/app/code/core/Mage/CatalogRule/Model/Rule.php @@ -27,10 +27,34 @@ class Mage_CatalogRule_Model_Rule extends Mage_Rule_Model_Rule { + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'catalogrule_rule'; + + /** + * Parameter name in event + * + * In observe method you can use $observer->getEvent()->getRule() in this case + * + * @var string + */ + protected $_eventObject = 'rule'; + + /** + * Matched product ids array + * + * @var array + */ protected $_productIds = array(); protected $_now; + /** + * Init resource model and id field + */ protected function _construct() { parent::_construct(); @@ -61,6 +85,7 @@ public function setNow($now) $this->_now = $now; } + public function toString($format='') { $str = Mage::helper('catalogrule')->__("Name: %s", $this->getName()) ."\n" @@ -94,46 +119,23 @@ public function toArray(array $arrAttributes = array()) return $out; } - /* - public function processProduct(Mage_Catalog_Model_Product $product) - { - $this->validateProduct($product) && $this->updateProduct($product); - return $this; - } - - public function validateProduct(Mage_Catalog_Model_Product $product) - { - if (!$this->getIsCollectionValidated()) { - $result = $result && $this->getIsActive() - && (strtotime($this->getFromDate()) <= $this->getNow()) - && (strtotime($this->getToDate()) >= $this->getNow()) - && ($this->getCustomerRegistered()==2 || $this->getCustomerRegistered()==$env->getCustomerRegistered()) - && ($this->getCustomerNewBuyer()==2 || $this->getCustomerNewBuyer()==$env->getCustomerNewBuyer()) - && $this->getConditions()->validateProduct($product); - } else { - $result = $this->getConditions()->validateProduct($product); - } - - return $result; - } - - public function updateProduct(Mage_Sales_Model_Product $product) - { - $this->getActions()->updateProduct($product); - return $this; - } - */ - public function getResourceCollection() - { - return Mage::getResourceModel('catalogrule/rule_collection'); - } + /** + * Process rule related data after rule save + * + * @return Mage_CatalogRule_Model_Rule + */ protected function _afterSave() { $this->_getResource()->updateRuleProductData($this); parent::_afterSave(); } + /** + * Get array of product ids which are matched by rule + * + * @return array + */ public function getMatchingProductIds() { if (empty($this->_productIds)) { @@ -159,6 +161,12 @@ public function getMatchingProductIds() return $this->_productIds; } + /** + * Callback function for product matching + * + * @param $args + * @return void + */ public function callbackValidateProduct($args) { $product = $args['product']->setData($args['row']); @@ -167,6 +175,13 @@ public function callbackValidateProduct($args) } } + /** + * Apply rule to product + * + * @param int|Mage_Catalog_Model_Product $product + * @param array $websiteIds + * @return void + */ public function applyToProduct($product, $websiteIds=null) { if (is_numeric($product)) { @@ -178,6 +193,11 @@ public function applyToProduct($product, $websiteIds=null) $this->getResource()->applyToProduct($this, $product, $websiteIds); } + /** + * Get array of assigned customer group ids + * + * @return array + */ public function getCustomerGroupIds() { $ids = $this->getData('customer_group_ids'); @@ -193,4 +213,18 @@ public function getCustomerGroupIds() } return $ids; } + + /** + * Apply all price rules and refresh price index + * + * @return Mage_CatalogRule_Model_Rule + */ + public function applyAll() + { + $this->_getResource()->applyAllRulesForDateRange(); + $indexProcess = Mage::getSingleton('index/indexer')->getProcessByCode('catalog_product_price'); + if ($indexProcess) { + $indexProcess->reindexAll(); + } + } } \ No newline at end of file diff --git a/app/code/core/Mage/CatalogRule/Model/Rule/Condition/Product.php b/app/code/core/Mage/CatalogRule/Model/Rule/Condition/Product.php index ba3cad9bdd..841af84b90 100644 --- a/app/code/core/Mage/CatalogRule/Model/Rule/Condition/Product.php +++ b/app/code/core/Mage/CatalogRule/Model/Rule/Condition/Product.php @@ -27,6 +27,13 @@ class Mage_CatalogRule_Model_Rule_Condition_Product extends Mage_Rule_Model_Condition_Abstract { + /** + * Attribute data key that indicates whether it should be used for rules + * + * @var string + */ + protected $_isUsedForRuleProperty = 'is_used_for_price_rules'; + /** * Retrieve attribute object * @@ -71,7 +78,7 @@ public function loadAttributeOptions() $attributes = array(); foreach ($productAttributes as $attribute) { /* @var $attribute Mage_Catalog_Model_Resource_Eav_Attribute */ - if (!$attribute->isAllowedForRuleCondition() || !$attribute->getIsUsedForPriceRules()) { + if (!$attribute->isAllowedForRuleCondition() || !$attribute->getDataUsingMethod($this->_isUsedForRuleProperty)) { continue; } $attributes[$attribute->getAttributeCode()] = $attribute->getFrontendLabel(); diff --git a/app/code/core/Mage/CatalogRule/Model/Rule/Product/Price.php b/app/code/core/Mage/CatalogRule/Model/Rule/Product/Price.php index 74ba142ced..d87fa39a0d 100644 --- a/app/code/core/Mage/CatalogRule/Model/Rule/Product/Price.php +++ b/app/code/core/Mage/CatalogRule/Model/Rule/Product/Price.php @@ -1,9 +1,66 @@ + */ class Mage_CatalogRule_Model_Rule_Product_Price extends Mage_Core_Model_Abstract { + /** + * Initialize resource model + * + */ protected function _construct() { - $this->_init('catalogrule/product_price'); + $this->_init('catalogrule/rule_product_price'); + } + + /** + * Apply price rule price to price index table + * + * @param Varien_Db_Select $select + * @param array|string $indexTable + * @param string $entityId + * @param string $customerGroupId + * @param string $websiteId + * @param array $updateFields the array fields for compare with rule price and update + * @param string $websiteDate + * @return Mage_CatalogRule_Model_Rule_Product_Price + */ + public function applyPriceRuleToIndexTable(Varien_Db_Select $select, $indexTable, $entityId, $customerGroupId, + $websiteId, $updateFields, $websiteDate) + { + $this->_getResource()->applyPriceRuleToIndexTable($select, $indexTable, $entityId, $customerGroupId, $websiteId, + $updateFields, $websiteDate); + + return $this; } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/CatalogRule/etc/adminhtml.xml b/app/code/core/Mage/CatalogRule/etc/adminhtml.xml new file mode 100644 index 0000000000..37a4b58208 --- /dev/null +++ b/app/code/core/Mage/CatalogRule/etc/adminhtml.xml @@ -0,0 +1,60 @@ + + + + + + Promotions + 50 + + + Catalog Price Rules + adminhtml/promo_catalog/ + promo/catalog + Mage_Catalog + + + + + + + + + + Promotions + 50 + + + Catalog Price Rules + + + + + + + + diff --git a/app/code/core/Mage/CatalogRule/etc/config.xml b/app/code/core/Mage/CatalogRule/etc/config.xml index 2032adc7d4..027ad63598 100644 --- a/app/code/core/Mage/CatalogRule/etc/config.xml +++ b/app/code/core/Mage/CatalogRule/etc/config.xml @@ -45,18 +45,10 @@ Mage_CatalogRule_Model_Mysql4 - - catalogrule - - - catalogrule_product - - - catalogrule_product_price - - - catalogrule_affected_product - + catalogrule + catalogrule_product + catalogrule_product_price + catalogrule_affected_product @@ -65,28 +57,24 @@ Mage_CatalogRule - - core_setup - - - - core_write - - - - - core_read - - + + + + + catalogrule/observer + prepareCatalogProductPriceIndexTable + + + + - singleton catalogrule/observer processFrontFinalPrice @@ -118,7 +106,6 @@ - singleton catalogrule/observer processAdminFinalPrice @@ -127,7 +114,6 @@ - singleton catalogrule/observer applyAllRulesOnProduct @@ -136,44 +122,12 @@ - singleton catalogrule/observer applyAllRules - - - Promotions - 50 - - - Catalog Price Rules - adminhtml/promo_catalog/ - promo/catalog - Mage_Catalog - - - - - - - - - - Promotions - 50 - - - Catalog Price Rules - - - - - - - @@ -187,7 +141,6 @@ - singleton catalogrule/observer processAdminFinalPrice diff --git a/app/code/core/Mage/CatalogSearch/Block/Advanced/Form.php b/app/code/core/Mage/CatalogSearch/Block/Advanced/Form.php index b0514d9b6e..fa09496365 100644 --- a/app/code/core/Mage/CatalogSearch/Block/Advanced/Form.php +++ b/app/code/core/Mage/CatalogSearch/Block/Advanced/Form.php @@ -71,7 +71,7 @@ public function getSearchableAttributes() */ public function getAttributeLabel($attribute) { - return Mage::helper('catalog')->__($attribute->getFrontend()->getLabel()); + return $attribute->getStoreLabel(); } /** @@ -262,7 +262,7 @@ public function getDateInput($attribute, $part = 'from') return $this->_getDateBlock() ->setName($name) - ->setId($attribute->getAttributeCode() . '_' . $part) + ->setId($attribute->getAttributeCode() . ($part == 'from' ? '' : '_' . $part)) ->setTitle($this->getAttributeLabel($attribute)) ->setValue($value) ->setImage($this->getSkinUrl('images/calendar.gif')) diff --git a/app/code/core/Mage/CatalogSearch/Block/Result.php b/app/code/core/Mage/CatalogSearch/Block/Result.php index 0ea80649a7..d828519137 100644 --- a/app/code/core/Mage/CatalogSearch/Block/Result.php +++ b/app/code/core/Mage/CatalogSearch/Block/Result.php @@ -181,7 +181,7 @@ public function getResultCount() public function getNoResultText() { if (Mage::helper('catalogsearch')->isMinQueryLength()) { - return Mage::helper('catalogsearch')->__('Minimum Search query length is %s', $this->_getQuery()->getMinQueryLenght()); + return Mage::helper('catalogsearch')->__('Minimum Search query length is %s', $this->_getQuery()->getMinQueryLength()); } return $this->_getData('no_result_text'); } diff --git a/app/code/core/Mage/CatalogSearch/Helper/Data.php b/app/code/core/Mage/CatalogSearch/Helper/Data.php index c8087a96ee..1c427af4fe 100644 --- a/app/code/core/Mage/CatalogSearch/Helper/Data.php +++ b/app/code/core/Mage/CatalogSearch/Helper/Data.php @@ -152,16 +152,18 @@ public function getSuggestCollection() } /** - * Retrieve result page url + * Retrieve result page url and set "secure" param to avoid confirm + * message when we submit form from secure page to unsecure * * @param string $query * @return string */ public function getResultUrl($query = null) { - return $this->_getUrl('catalogsearch/result', array('_query' => array( - self::QUERY_VAR_NAME => $query - ))); + return $this->_getUrl('catalogsearch/result', array( + '_query' => array(self::QUERY_VAR_NAME => $query), + '_secure' => true + )); } /** diff --git a/app/code/core/Mage/CatalogSearch/Model/Advanced.php b/app/code/core/Mage/CatalogSearch/Model/Advanced.php index 91c8c5f1f9..fb96c8699c 100644 --- a/app/code/core/Mage/CatalogSearch/Model/Advanced.php +++ b/app/code/core/Mage/CatalogSearch/Model/Advanced.php @@ -42,15 +42,16 @@ class Mage_CatalogSearch_Model_Advanced extends Varien_Object public function getAttributes() { + /* @var $attributes Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Collection */ $attributes = $this->getData('attributes'); if (is_null($attributes)) { $product = Mage::getModel('catalog/product'); - $attributes = Mage::getResourceModel('eav/entity_attribute_collection') - ->setEntityTypeFilter($product->getResource()->getTypeId()) + $attributes = Mage::getResourceModel('catalog/product_attribute_collection') //->addIsSearchableFilter() ->addHasOptionsFilter() ->addDisplayInAdvancedSearchFilter() - ->setOrder('attribute_id', 'asc') + ->addStoreLabel(Mage::app()->getStore()->getId()) + ->setOrder('main_table.attribute_id', 'asc') ->load(); foreach ($attributes as $attribute) { $attribute->setEntity($product->getResource()); @@ -162,7 +163,7 @@ public function addFilters($values) */ protected function _addSearchCriteria($attribute, $value) { - $name = $attribute->getFrontend()->getLabel(); + $name = $attribute->getStoreLabel(); if (is_array($value) && (isset($value['from']) || isset($value['to']))){ if (isset($value['currency'])) { diff --git a/app/code/core/Mage/CatalogSearch/Model/Fulltext.php b/app/code/core/Mage/CatalogSearch/Model/Fulltext.php index 20fb78a91b..82402e6be1 100644 --- a/app/code/core/Mage/CatalogSearch/Model/Fulltext.php +++ b/app/code/core/Mage/CatalogSearch/Model/Fulltext.php @@ -104,8 +104,8 @@ public function prepareResult($query = null) $query = Mage::helper('catalogsearch')->getQuery(); } $queryText = Mage::helper('catalogsearch')->getQueryText(); - if ($query->getSynonimFor()) { - $queryText = $query->getSynonimFor(); + if ($query->getSynonymFor()) { + $queryText = $query->getSynonymFor(); } $this->getResource()->prepareResult($this, $queryText, $query); return $this; diff --git a/app/code/core/Mage/CatalogSearch/Model/Indexer/Fulltext.php b/app/code/core/Mage/CatalogSearch/Model/Indexer/Fulltext.php new file mode 100644 index 0000000000..338a2e8b81 --- /dev/null +++ b/app/code/core/Mage/CatalogSearch/Model/Indexer/Fulltext.php @@ -0,0 +1,302 @@ + + */ +class Mage_CatalogSearch_Model_Indexer_Fulltext extends Mage_Index_Model_Indexer_Abstract +{ + /** + * Indexer must be match entities + * + * @var array + */ + protected $_matchedEntities = array( + Mage_Catalog_Model_Product::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE, + Mage_Index_Model_Event::TYPE_MASS_ACTION, + Mage_Index_Model_Event::TYPE_DELETE + ), + Mage_Catalog_Model_Resource_Eav_Attribute::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE, + Mage_Index_Model_Event::TYPE_DELETE, + ), + Mage_Core_Model_Store::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE, + Mage_Index_Model_Event::TYPE_DELETE + ), + Mage_Core_Model_Store_Group::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Core_Model_Config_Data::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ), + Mage_Catalog_Model_Convert_Adapter_Product::ENTITY => array( + Mage_Index_Model_Event::TYPE_SAVE + ) + ); + + /** + * Related Configuration Settings for match + * + * @var array + */ + protected $_relatedConfigSettings = array( + Mage_CatalogSearch_Model_Fulltext::XML_PATH_CATALOG_SEARCH_TYPE + ); + + /** + * Retrieve Fulltext Search instance + * + * @return Mage_CatalogSearch_Model_Fulltext + */ + protected function _getIndexer() + { + return Mage::getSingleton('catalogsearch/fulltext'); + } + + /** + * Retrieve Indexer name + * + * @return string + */ + public function getName() + { + return Mage::helper('catalogsearch')->__('Catalog Search Index'); + } + + /** + * Retrieve Indexer description + * + * @return string + */ + public function getDescription() + { + return Mage::helper('catalogsearch')->__('Rebuild Catalog product fulltext search index'); + } + + /** + * Check if event can be matched by process + * Overwrote for check is flat catalog product is enabled and specific save + * attribute, store, store_group + * + * @param Mage_Index_Model_Event $event + * @return bool + */ + public function matchEvent(Mage_Index_Model_Event $event) + { + $entity = $event->getEntity(); + if ($entity == Mage_Catalog_Model_Resource_Eav_Attribute::ENTITY) { + /* @var $attribute Mage_Catalog_Model_Resource_Eav_Attribute */ + $attribute = $event->getDataObject(); + + if ($event->getType() == Mage_Index_Model_Event::TYPE_SAVE) { + return $attribute->dataHasChangedFor('is_searchable'); + } else if ($event->getType() == Mage_Index_Model_Event::TYPE_DELETE) { + return $attribute->getIsSearchable(); + } + } else if ($entity == Mage_Core_Model_Store::ENTITY) { + if ($event->getType() == Mage_Index_Model_Event::TYPE_DELETE) { + return true; + } + + /* @var $store Mage_Core_Model_Store */ + $store = $event->getDataObject(); + if ($store->isObjectNew()) { + return true; + } + return false; + } else if ($entity == Mage_Core_Model_Store_Group::ENTITY) { + /* @var $storeGroup Mage_Core_Model_Store_Group */ + $storeGroup = $event->getDataObject(); + if ($storeGroup->dataHasChangedFor('website_id')) { + return true; + } + return false; + } else if ($entity == Mage_Core_Model_Config_Data::ENTITY) { + $data = $event->getDataObject(); + if (in_array($data->getPath(), $this->_relatedConfigSettings)) { + return $data->isValueChanged(); + } else { + return false; + } + } + + return parent::matchEvent($event); + } + + /** + * Register data required by process in event object + * + * @param Mage_Index_Model_Event $event + */ + protected function _registerEvent(Mage_Index_Model_Event $event) + { + switch ($event->getEntity()) { + case Mage_Catalog_Model_Product::ENTITY: + $this->_registerCatalogProductEvent($event); + break; + + case Mage_Catalog_Model_Convert_Adapter_Product::ENTITY: + $event->addNewData('catalogsearch_fulltext_reindex_all', true); + break; + + case Mage_Core_Model_Config_Data::ENTITY: + case Mage_Core_Model_Store::ENTITY: + case Mage_Catalog_Model_Resource_Eav_Attribute::ENTITY: + case Mage_Core_Model_Store_Group::ENTITY: + $event->addNewData('catalogsearch_fulltext_skip_call_event_handler', true); + $process = $event->getProcess(); + $process->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX); + break; + } + } + + /** + * Register data required by catatalog product process in event object + * + * @param Mage_Index_Model_Event $event + * @return Mage_CatalogSearch_Model_Indexer_Search + */ + protected function _registerCatalogProductEvent(Mage_Index_Model_Event $event) + { + switch ($event->getType()) { + case Mage_Index_Model_Event::TYPE_SAVE: + /* @var $product Mage_Catalog_Model_Product */ + $product = $event->getDataObject(); + + $event->addNewData('catalogsearch_update_product_id', $product->getId()); + break; + case Mage_Index_Model_Event::TYPE_DELETE: + /* @var $product Mage_Catalog_Model_Product */ + $product = $event->getDataObject(); + + $event->addNewData('catalogsearch_delete_product_id', $product->getId()); + break; + case Mage_Index_Model_Event::TYPE_MASS_ACTION: + /* @var $actionObject Varien_Object */ + $actionObject = $event->getDataObject(); + + $reindexData = array(); + $rebuildIndex = false; + + // check if status changed + $attrData = $actionObject->getAttributesData(); + if (isset($attrData['status'])) { + $rebuildIndex = true; + $reindexData['catalogsearch_status'] = $attrData['status']; + } + + // check changed websites + if ($actionObject->getWebsiteIds()) { + $rebuildIndex = true; + $reindexData['catalogsearch_website_ids'] = $actionObject->getWebsiteIds(); + $reindexData['catalogsearch_action_type'] = $actionObject->getActionType(); + } + + // register affected products + if ($rebuildIndex) { + $reindexData['catalogsearch_product_ids'] = $actionObject->getProductIds(); + foreach ($reindexData as $k => $v) { + $event->addNewData($k, $v); + } + } + break; + } + + return $this; + } + + /** + * Process event + * + * @param Mage_Index_Model_Event $event + */ + protected function _processEvent(Mage_Index_Model_Event $event) + { + $data = $event->getNewData(); + + if (!empty($data['catalogsearch_fulltext_reindex_all'])) { + $this->reindexAll(); + } else if (!empty($data['catalogsearch_delete_product_id'])) { + $productId = $data['catalogsearch_delete_product_id']; + $this->_getIndexer()->cleanIndex(null, $productId) + ->resetSearchResults(); + } else if (!empty($data['catalogsearch_update_product_id'])) { + $productId = $data['catalogsearch_update_product_id']; + $this->_getIndexer()->rebuildIndex(null, $productId) + ->resetSearchResults(); + } else if (!empty($data['catalogsearch_product_ids'])) { + // mass action + $productIds = $data['catalogsearch_product_ids']; + + if (!empty($data['catalogsearch_website_ids'])) { + $websiteIds = $data['catalogsearch_website_ids']; + $actionType = $data['catalogsearch_action_type']; + + foreach ($websiteIds as $websiteId) { + foreach (Mage::app()->getWebsite($websiteId)->getStoreIds() as $storeId) { + if ($actionType == 'remove') { + $this->_getIndexer() + ->cleanIndex($storeId, $productIds) + ->resetSearchResults(); + } else if ($actionType == 'add') { + $this->_getIndexer() + ->rebuildIndex($storeId, $productIds) + ->resetSearchResults(); + } + } + } + } + if (isset($data['catalogsearch_status'])) { + $status = $data['catalogsearch_status']; + if ($status == Mage_Catalog_Model_Product_Status::STATUS_ENABLED) { + $this->_getIndexer() + ->rebuildIndex(null, $productIds) + ->resetSearchResults(); + } else { + $this->_getIndexer() + ->cleanIndex(null, $productIds) + ->resetSearchResults(); + } + } + } + } + + /** + * Rebuild all index data + * + */ + public function reindexAll() + { + $this->_getIndexer()->rebuildIndex(); + } +} diff --git a/app/code/core/Mage/CatalogSearch/Model/Layer.php b/app/code/core/Mage/CatalogSearch/Model/Layer.php index 70aa2fbc1a..fb3c7b96d6 100644 --- a/app/code/core/Mage/CatalogSearch/Model/Layer.php +++ b/app/code/core/Mage/CatalogSearch/Model/Layer.php @@ -56,7 +56,7 @@ public function getProductCollection() public function prepareProductCollection($collection) { $collection->addAttributeToSelect(Mage::getSingleton('catalog/config')->getProductAttributes()) - ->addSearchFilter(Mage::helper('catalogsearch')->getEscapedQueryText()) + ->addSearchFilter(Mage::helper('catalogsearch')->getQueryText()) ->setStore(Mage::app()->getStore()) ->addMinimalPrice() ->addFinalPrice() @@ -99,8 +99,8 @@ public function getStateTags(array $additionalTags = array()) /** * Add filters to attribute collection * - * @param Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection + * @param Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Collection $collection + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Collection */ protected function _prepareAttributeCollection($collection) { diff --git a/app/code/core/Mage/CatalogSearch/Model/Mysql4/Advanced/Collection.php b/app/code/core/Mage/CatalogSearch/Model/Mysql4/Advanced/Collection.php index bff34011b5..8cfc83be60 100644 --- a/app/code/core/Mage/CatalogSearch/Model/Mysql4/Advanced/Collection.php +++ b/app/code/core/Mage/CatalogSearch/Model/Mysql4/Advanced/Collection.php @@ -89,10 +89,10 @@ public function addFieldsToFilter($fields) foreach ($conditionData as $data) { if (is_array($data)) { - $select->where('IFNULL(t2.value, t1.value) ' . $data[0], $data[1]); + $select->where('IF(t2.value_id>0, t2.value, t1.value) ' . $data[0], $data[1]); } else { - $select->where('IFNULL(t2.value, t1.value) ' . $data); + $select->where('IF(t2.value_id>0, t2.value, t1.value) ' . $data); } } } diff --git a/app/code/core/Mage/CatalogSearch/Model/Mysql4/Fulltext.php b/app/code/core/Mage/CatalogSearch/Model/Mysql4/Fulltext.php index cf2f9804ea..dbb17d972a 100644 --- a/app/code/core/Mage/CatalogSearch/Model/Mysql4/Fulltext.php +++ b/app/code/core/Mage/CatalogSearch/Model/Mysql4/Fulltext.php @@ -345,13 +345,17 @@ protected function _getSearchableAttributes($backendType = null) $entity = $entityType->getEntity(); $whereCond = array( - $this->_getWriteAdapter()->quoteInto('is_searchable=?', 1), - $this->_getWriteAdapter()->quoteInto('attribute_code IN(?)', array('status', 'visibility')) + $this->_getWriteAdapter()->quoteInto('additional_table.is_searchable=?', 1), + $this->_getWriteAdapter()->quoteInto('main_table.attribute_code IN(?)', array('status', 'visibility')) ); $select = $this->_getWriteAdapter()->select() - ->from($this->getTable('eav/attribute')) - ->where('entity_type_id=?', $entityType->getEntityTypeId()) + ->from(array('main_table' => $this->getTable('eav/attribute'))) + ->join( + array('additional_table' => $this->getTable('catalog/eav_attribute')), + 'additional_table.attribute_id = main_table.attribute_id' + ) + ->where('main_table.entity_type_id=?', $entityType->getEntityTypeId()) ->where(join(' OR ', $whereCond)); $attributesData = $this->_getWriteAdapter()->fetchAll($select); $this->getEavConfig()->importAttributesData($entityType, $attributesData); @@ -422,7 +426,7 @@ protected function _getProductAttributes($storeId, array $productIds, array $atr ->joinLeft( array('t_store' => $tableName), $this->_getWriteAdapter()->quoteInto("t_default.entity_id=t_store.entity_id AND t_default.attribute_id=t_store.attribute_id AND t_store.store_id=?", $storeId), - array('value'=>'IFNULL(t_store.value, t_default.value)')) + array('value'=>'IF(t_store.value_id > 0, t_store.value, t_default.value)')) ->where('t_default.store_id=?', 0) ->where('t_default.attribute_id IN(?)', $attributeIds) ->where('t_default.entity_id IN(?)', $productIds); diff --git a/app/code/core/Mage/CatalogSearch/Model/Mysql4/Query/Collection.php b/app/code/core/Mage/CatalogSearch/Model/Mysql4/Query/Collection.php index 26368a078e..0ac955205e 100644 --- a/app/code/core/Mage/CatalogSearch/Model/Mysql4/Query/Collection.php +++ b/app/code/core/Mage/CatalogSearch/Model/Mysql4/Query/Collection.php @@ -83,7 +83,7 @@ public function setQueryFilter($query) $this->getSelect()->reset(Zend_Db_Select::FROM)->distinct(true) ->from( array('main_table' => $this->getTable('catalogsearch/search_query')), - array('query' => "IF(IFNULL(synonim_for,'')<>'', synonim_for, query_text)", 'num_results') + array('query' => "IF(IFNULL(synonym_for,'')<>'', synonym_for, query_text)", 'num_results') ) ->where('num_results>0 AND display_in_terms=1 AND query_text LIKE ?', $query.'%') ->order('popularity desc'); @@ -105,7 +105,7 @@ public function setPopularQueryFilter($storeIds = null) $this->getSelect()->reset(Zend_Db_Select::FROM)->distinct(true) ->from( array('main_table'=>$this->getTable('catalogsearch/search_query')), - array('name'=>"if(ifnull(synonim_for,'')<>'', synonim_for, query_text)", 'num_results') + array('name'=>"if(ifnull(synonym_for,'')<>'', synonym_for, query_text)", 'num_results') ); if ($storeIds) { $this->addStoreFilter($storeIds); diff --git a/app/code/core/Mage/CatalogSearch/Model/Mysql4/Search/Collection.php b/app/code/core/Mage/CatalogSearch/Model/Mysql4/Search/Collection.php index 3749e9542e..be6598a2d1 100644 --- a/app/code/core/Mage/CatalogSearch/Model/Mysql4/Search/Collection.php +++ b/app/code/core/Mage/CatalogSearch/Model/Mysql4/Search/Collection.php @@ -52,8 +52,7 @@ public function addSearchFilter($query) protected function _getAttributesCollection() { if (!$this->_attributesCollection) { - $this->_attributesCollection = Mage::getResourceModel('eav/entity_attribute_collection') - ->setEntityTypeFilter($this->getEntity()->getTypeId()) + $this->_attributesCollection = Mage::getResourceModel('catalog/product_attribute_collection') ->load(); foreach ($this->_attributesCollection as $attribute) { @@ -118,7 +117,7 @@ protected function _getSearchEntityIdsSql($query) ) ->where('t1.attribute_id IN (?)', $attributeIds) ->where('t1.store_id = ?', 0) - ->where('IFNULL(t2.value, t1.value) LIKE :'.$param); + ->where('IF(t2.value_id>0, t2.value, t1.value) LIKE :'.$param); $this->addBindParam($param, $this->_searchQuery); } @@ -173,7 +172,7 @@ protected function _getSearchInOptionSql($query) ->join(array('a' => $attributesTable), 'option.attribute_id=a.attribute_id', array()) ->where('default.store_id=0') ->where('option.attribute_id IN (?)', $attributeIds) - ->where('IFNULL(store.value, default.value) LIKE :search_query'); + ->where('IF(store.value_id>0, store.value, default.value) LIKE :search_query'); $options = $this->getConnection()->fetchAll($select, array('search_query'=>$this->_searchQuery)); if (empty($options)) { return false; diff --git a/app/code/core/Mage/CatalogSearch/Model/Query.php b/app/code/core/Mage/CatalogSearch/Model/Query.php index b9a8aacedc..1e28361c3a 100644 --- a/app/code/core/Mage/CatalogSearch/Model/Query.php +++ b/app/code/core/Mage/CatalogSearch/Model/Query.php @@ -56,7 +56,7 @@ public function getResultCollection() if (is_null($collection)) { $collection = Mage::getResourceModel('catalogsearch/search_collection'); - $text = $this->getSynonimFor(); + $text = $this->getSynonymFor(); if (!$text) { $text = $this->getQueryText(); } @@ -145,6 +145,7 @@ public function prepare() /** * Retrieve minimum query length * + * @deprecated after 1.3.2.3 use getMinQueryLength() instead * @return int */ public function getMinQueryLenght() @@ -152,9 +153,19 @@ public function getMinQueryLenght() return Mage::getStoreConfig(self::XML_PATH_MIN_QUERY_LENGTH, $this->getStoreId()); } + /** + * Retrieve minimum query length + * + * @return int + */ + public function getMinQueryLength(){ + return $this->getMinQueryLenght(); + } + /** * Retrieve maximum query length * + * @deprecated after 1.3.2.3 use getMaxQueryLength() instead * @return int */ public function getMaxQueryLenght() @@ -162,6 +173,16 @@ public function getMaxQueryLenght() return Mage::getStoreConfig(self::XML_PATH_MAX_QUERY_LENGTH, $this->getStoreId()); } + /** + * Retrieve maximum query length + * + * @return int + */ + public function getMaxQueryLength() + { + return $this->getMaxQueryLenght(); + } + /** * Retrieve maximum query words for like search * diff --git a/app/code/core/Mage/CatalogSearch/Model/System/Config/Backend/Sitemap.php b/app/code/core/Mage/CatalogSearch/Model/System/Config/Backend/Sitemap.php new file mode 100644 index 0000000000..9582d608af --- /dev/null +++ b/app/code/core/Mage/CatalogSearch/Model/System/Config/Backend/Sitemap.php @@ -0,0 +1,40 @@ +getRequest()->getParam('q', false)) { + $this->getResponse()->setRedirect(Mage::getSingleton('core/url')->getBaseUrl()); + } + $this->getResponse()->setBody($this->getLayout()->createBlock('catalogsearch/autocomplete')->toHtml()); } } diff --git a/app/code/core/Mage/CatalogSearch/etc/adminhtml.xml b/app/code/core/Mage/CatalogSearch/etc/adminhtml.xml new file mode 100644 index 0000000000..6a84040f6d --- /dev/null +++ b/app/code/core/Mage/CatalogSearch/etc/adminhtml.xml @@ -0,0 +1,54 @@ + + + + + + + + Search + adminhtml/catalog_search/ + + + + + + + + + + + + Search + + + + + + + + diff --git a/app/code/core/Mage/CatalogSearch/etc/config.xml b/app/code/core/Mage/CatalogSearch/etc/config.xml index 31f84fb35b..1cbab3d672 100644 --- a/app/code/core/Mage/CatalogSearch/etc/config.xml +++ b/app/code/core/Mage/CatalogSearch/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.6 + 0.7.7 @@ -63,26 +63,12 @@ Mage_CatalogSearch - - core_setup - - - - core_write - - - - - core_read - - - + Mage_CatalogSearch_Block + + + + catalogsearch/indexer_fulltext + + + @@ -193,31 +179,6 @@ - - - - - Search - adminhtml/catalog_search/ - - - - - - - - - - - - Search - - - - - - - diff --git a/app/code/core/Mage/CatalogSearch/etc/system.xml b/app/code/core/Mage/CatalogSearch/etc/system.xml index 3abfd4af6e..9e8c792b47 100644 --- a/app/code/core/Mage/CatalogSearch/etc/system.xml +++ b/app/code/core/Mage/CatalogSearch/etc/system.xml @@ -44,6 +44,7 @@ Autogenerated site map select adminhtml/system_config_source_enabledisable + catalogsearch/system_config_backend_sitemap 1 1 1 diff --git a/app/code/core/Mage/CatalogSearch/sql/catalogsearch_setup/mysql4-upgrade-0.7.6-0.7.7.php b/app/code/core/Mage/CatalogSearch/sql/catalogsearch_setup/mysql4-upgrade-0.7.6-0.7.7.php new file mode 100644 index 0000000000..ad451f144b --- /dev/null +++ b/app/code/core/Mage/CatalogSearch/sql/catalogsearch_setup/mysql4-upgrade-0.7.6-0.7.7.php @@ -0,0 +1,36 @@ +startSetup(); + +$table = $installer->getTable('catalogsearch_query'); + +$installer->getConnection()->changeColumn($table, 'synonim_for', 'synonym_for', 'VARCHAR( 255 ) NOT NULL'); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Checkout/Block/Cart.php b/app/code/core/Mage/Checkout/Block/Cart.php index eac84b80ce..59ef26eda1 100644 --- a/app/code/core/Mage/Checkout/Block/Cart.php +++ b/app/code/core/Mage/Checkout/Block/Cart.php @@ -71,7 +71,7 @@ public function __construct() if (isset($products[$product->getId()])) { $object = new Varien_Object($products[$product->getId()]); - $item->setUrlDataObject($object); + $item->getProduct()->setUrlDataObject($object); } } } diff --git a/app/code/core/Mage/Checkout/Block/Cart/Item/Renderer.php b/app/code/core/Mage/Checkout/Block/Cart/Item/Renderer.php index 74f3b3d025..71e11c3ccc 100644 --- a/app/code/core/Mage/Checkout/Block/Cart/Item/Renderer.php +++ b/app/code/core/Mage/Checkout/Block/Cart/Item/Renderer.php @@ -115,8 +115,8 @@ public function hasProductUrl() return true; } else { - if ($this->getItem()->hasUrlDataObject()) { - $data = $this->getItem()->getUrlDataObject(); + if ($product->hasUrlDataObject()) { + $data = $product->getUrlDataObject(); if (in_array($data->getVisibility(), $product->getVisibleInSiteVisibilities())) { return true; } diff --git a/app/code/core/Mage/Checkout/Model/Cart.php b/app/code/core/Mage/Checkout/Model/Cart.php index d767a355cf..32e9d4baf3 100644 --- a/app/code/core/Mage/Checkout/Model/Cart.php +++ b/app/code/core/Mage/Checkout/Model/Cart.php @@ -211,6 +211,24 @@ public function addProduct($product, $info=null) $product = $this->_getProduct($product); $request = $this->_getProductRequest($info); + //Check if current product already exists in cart + $productId = $product->getId(); + $items = $this->getQuote()->getAllItems(); + $quoteProduct = null; + foreach ($items as $item) { + if ($item->getProductId() == $productId) { + $quoteProduct = $item; + break; + } + } + + if ($product->getStockItem()) { + $minimumQty = $product->getStockItem()->getMinSaleQty(); + //If product was not found in cart and there is set minimal qty for it + if($minimumQty > 0 && $request->getQty() < $minimumQty && $quoteProduct === null){ + $request->setQty($minimumQty); + } + } if ($product->getId()) { diff --git a/app/code/core/Mage/Checkout/Model/Mysql4/Setup.php b/app/code/core/Mage/Checkout/Model/Mysql4/Setup.php new file mode 100644 index 0000000000..abef24ef33 --- /dev/null +++ b/app/code/core/Mage/Checkout/Model/Mysql4/Setup.php @@ -0,0 +1,37 @@ + + */ +class Mage_Checkout_Model_Mysql4_Setup extends Mage_Eav_Model_Entity_Setup +{ +} diff --git a/app/code/core/Mage/Checkout/Model/Session.php b/app/code/core/Mage/Checkout/Model/Session.php index 6837502601..c506d4bf56 100644 --- a/app/code/core/Mage/Checkout/Model/Session.php +++ b/app/code/core/Mage/Checkout/Model/Session.php @@ -84,6 +84,8 @@ public function getQuote() if ($remoteAddr = Mage::helper('core/http')->getRemoteAddr()) { $this->_quote->setRemoteIp($remoteAddr); + $xForwardIp = Mage::app()->getRequest()->getServer('HTTP_X_FORWARDED_FOR'); + $this->_quote->setXForwardedFor($xForwardIp); } return $this->_quote; } diff --git a/app/code/core/Mage/Checkout/Model/Type/Abstract.php b/app/code/core/Mage/Checkout/Model/Type/Abstract.php index ee7921700f..93c089dc81 100644 --- a/app/code/core/Mage/Checkout/Model/Type/Abstract.php +++ b/app/code/core/Mage/Checkout/Model/Type/Abstract.php @@ -125,6 +125,13 @@ public function getCustomerDefaultBillingAddress() $address = $this->getData('customer_default_billing_address'); if (is_null($address)) { $address = $this->getCustomer()->getDefaultBillingAddress(); + if (!$address) { + foreach ($this->getCustomer()->getAddresses() as $address) { + if($address){ + break; + } + } + } $this->setData('customer_default_billing_address', $address); } return $address; diff --git a/app/code/core/Mage/Checkout/Model/Type/Multishipping.php b/app/code/core/Mage/Checkout/Model/Type/Multishipping.php index 5f51e06c1f..94b85dc923 100644 --- a/app/code/core/Mage/Checkout/Model/Type/Multishipping.php +++ b/app/code/core/Mage/Checkout/Model/Type/Multishipping.php @@ -98,8 +98,9 @@ protected function _init() } $this->save(); + } else { + $this->getQuote()->collectTotals(); } - $this->getQuote()->collectTotals(); return $this; } diff --git a/app/code/core/Mage/Checkout/Model/Type/Onepage.php b/app/code/core/Mage/Checkout/Model/Type/Onepage.php index f0f356ee3d..d7408d45d0 100644 --- a/app/code/core/Mage/Checkout/Model/Type/Onepage.php +++ b/app/code/core/Mage/Checkout/Model/Type/Onepage.php @@ -228,6 +228,11 @@ protected function _processValidateCustomer(Mage_Sales_Model_Quote_Address $addr $this->getQuote()->setCustomerTaxvat($address->getTaxvat()); } + // set customer gender for further usage + if ($address->getGender()) { + $this->getQuote()->setCustomerGender($address->getGender()); + } + // invoke customer model, if it is registering if (Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER == $this->getQuote()->getCheckoutMethod()) { // set customer password hash for further usage @@ -242,6 +247,7 @@ protected function _processValidateCustomer(Mage_Sales_Model_Quote_Address $addr 'password' => 'customer_password', 'confirmation' => 'confirm_password', 'taxvat' => 'taxvat', + 'gender' => 'gender', ) as $key => $dataKey) { $customer->setData($key, $address->getData($dataKey)); } @@ -349,10 +355,16 @@ public function savePayment($data) ); return $res; } + if ($this->getQuote()->isVirtual()) { + $this->getQuote()->getBillingAddress()->setPaymentMethod(isset($data['method']) ? $data['method'] : null); + } + else { + $this->getQuote()->getShippingAddress()->setPaymentMethod(isset($data['method']) ? $data['method'] : null); + } + $payment = $this->getQuote()->getPayment(); $payment->importData($data); - $this->getQuote()->getShippingAddress()->setPaymentMethod($payment->getMethod()); $this->getQuote()->save(); $this->getCheckout() @@ -436,6 +448,10 @@ public function saveOrder() $billing->setCustomerTaxvat($this->getQuote()->getCustomerTaxvat()); } + if ($this->getQuote()->getCustomerGender() && !$billing->getCustomerGender()) { + $billing->setCustomerGender($this->getQuote()->getCustomerGender()); + } + Mage::helper('core')->copyFieldset('checkout_onepage_billing', 'to_customer', $billing, $customer); $customer->setPassword($customer->decryptPassword($this->getQuote()->getPasswordHash())); diff --git a/app/code/core/Mage/Checkout/controllers/MultishippingController.php b/app/code/core/Mage/Checkout/controllers/MultishippingController.php index 706922e2b1..d48d0da0c2 100644 --- a/app/code/core/Mage/Checkout/controllers/MultishippingController.php +++ b/app/code/core/Mage/Checkout/controllers/MultishippingController.php @@ -404,7 +404,6 @@ public function overviewAction() try { $payment = $this->getRequest()->getPost('payment'); $this->_getCheckout()->setPaymentMethod($payment); - $this->_getCheckout()->getQuote()->getPayment()->importData($payment); $this->_getState()->setCompleteStep( Mage_Checkout_Model_Type_Multishipping_State::STEP_BILLING @@ -485,6 +484,7 @@ public function successAction() $this->loadLayout(); $this->_initLayoutMessages('checkout/session'); + Mage::dispatchEvent('checkout_multishipping_controller_success_action'); $this->renderLayout(); } diff --git a/app/code/core/Mage/Checkout/controllers/OnepageController.php b/app/code/core/Mage/Checkout/controllers/OnepageController.php index 8d79e23af8..3aa7b1e803 100644 --- a/app/code/core/Mage/Checkout/controllers/OnepageController.php +++ b/app/code/core/Mage/Checkout/controllers/OnepageController.php @@ -268,6 +268,9 @@ public function saveBillingAction() if ($this->getRequest()->isPost()) { $data = $this->getRequest()->getPost('billing', array()); $customerAddressId = $this->getRequest()->getPost('billing_address_id', false); + if (isset($data['email'])) { + $data['email'] = trim($data['email']); + } $result = $this->getOnepage()->saveBilling($data, $customerAddressId); if (!isset($result['error'])) { diff --git a/app/code/core/Mage/Checkout/etc/adminhtml.xml b/app/code/core/Mage/Checkout/etc/adminhtml.xml new file mode 100644 index 0000000000..404bb66c31 --- /dev/null +++ b/app/code/core/Mage/Checkout/etc/adminhtml.xml @@ -0,0 +1,69 @@ + + + + + + + + 100 + Terms and conditions + adminhtml/checkout_agreement/ + checkout/agreement + + + + + + + + + + + + Terms and Conditions + 50 + + + + + + + + + Checkout Section + 70 + + + + + + + + + + diff --git a/app/code/core/Mage/Checkout/etc/config.xml b/app/code/core/Mage/Checkout/etc/config.xml index 7516716b3c..b863e29722 100644 --- a/app/code/core/Mage/Checkout/etc/config.xml +++ b/app/code/core/Mage/Checkout/etc/config.xml @@ -28,7 +28,7 @@ - 0.9.3 + 0.9.4 @@ -52,6 +52,7 @@ * dob taxvat + gender @@ -74,21 +75,9 @@ Mage_Checkout + Mage_Checkout_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - @@ -109,7 +98,6 @@ - singleton checkout/observer loadCustomerQuote @@ -118,7 +106,6 @@ - singleton checkout/observer unsetAll @@ -127,7 +114,6 @@ - singleton checkout/observer salesQuoteSaveAfter @@ -161,46 +147,6 @@ - - - - - 100 - Terms and conditions - adminhtml/checkout_agreement/ - checkout/agreement - - - - - - - - - - - - Terms and Conditions - 50 - - - - - - - - - Checkout Section - 70 - - - - - - - - - diff --git a/app/code/core/Mage/Checkout/sql/checkout_setup/mysql4-upgrade-0.9.3-0.9.4.php b/app/code/core/Mage/Checkout/sql/checkout_setup/mysql4-upgrade-0.9.3-0.9.4.php new file mode 100644 index 0000000000..5a7d39b21f --- /dev/null +++ b/app/code/core/Mage/Checkout/sql/checkout_setup/mysql4-upgrade-0.9.3-0.9.4.php @@ -0,0 +1,702 @@ +startSetup(); +$setup = $installer->getConnection(); + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/prefix_show') + ->where('value!=?', '0'); +$showPrefix = (bool)Mage::helper('customer/address')->getConfig('prefix_show') + || ($setup->fetchOne($select) > 0); + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/middlename_show') + ->where('value!=?', '0'); +$showMiddlename = (bool)Mage::helper('customer/address')->getConfig('middlename_show') + || ($setup->fetchOne($select) > 0); + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/suffix_show') + ->where('value!=?', '0'); +$showSuffix = (bool)Mage::helper('customer/address')->getConfig('suffix_show') + || ($setup->fetchOne($select) > 0); + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/dob_show') + ->where('value!=?', '0'); +$showDob = (bool)Mage::helper('customer/address')->getConfig('dob_show') + || ($setup->fetchOne($select) > 0); + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/taxvat_show') + ->where('value!=?', '0'); +$showTaxVat = (bool)Mage::helper('customer/address')->getConfig('taxvat_show') + || ($setup->fetchOne($select) > 0); + +$customerEntityTypeId = $installer->getEntityTypeId('customer'); +$addressEntityTypeId = $installer->getEntityTypeId('customer_address'); + +/** + ***************************************************************************** + * checkout/onepage/register + ***************************************************************************** + */ + +$setup->insert($installer->getTable('eav/form_type'), array( + 'code' => 'checkout_onepage_register', + 'label' => 'checkout_onepage_register', + 'is_system' => 1, + 'theme' => '', + 'store_id' => 0 +)); +$formTypeId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $customerEntityTypeId +)); +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $addressEntityTypeId +)); + +$elementSort = 0; +if ($showPrefix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'prefix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'firstname'), + 'sort_order' => $elementSort++ +)); +if ($showMiddlename) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'middlename'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'lastname'), + 'sort_order' => $elementSort++ +)); +if ($showSuffix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'suffix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'company'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'email'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'street'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'city'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'region'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'postcode'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'country_id'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'telephone'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'fax'), + 'sort_order' => $elementSort++ +)); +if ($showDob) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'dob'), + 'sort_order' => $elementSort++ + )); +} +if ($showTaxVat) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'taxvat'), + 'sort_order' => $elementSort++ + )); +} + +/** + ***************************************************************************** + * checkout/onepage/register_guest + ***************************************************************************** + */ + +$setup->insert($installer->getTable('eav/form_type'), array( + 'code' => 'checkout_onepage_register_guest', + 'label' => 'checkout_onepage_register_guest', + 'is_system' => 1, + 'theme' => '', + 'store_id' => 0 +)); +$formTypeId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $customerEntityTypeId +)); +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $addressEntityTypeId +)); + +$elementSort = 0; +if ($showPrefix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'prefix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'firstname'), + 'sort_order' => $elementSort++ +)); +if ($showMiddlename) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'middlename'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'lastname'), + 'sort_order' => $elementSort++ +)); +if ($showSuffix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'suffix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'company'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'email'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'street'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'city'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'region'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'postcode'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'country_id'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'telephone'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'fax'), + 'sort_order' => $elementSort++ +)); +if ($showDob) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'dob'), + 'sort_order' => $elementSort++ + )); +} +if ($showTaxVat) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'taxvat'), + 'sort_order' => $elementSort++ + )); +} + +/** + ***************************************************************************** + * checkout/onepage/billing_address + ***************************************************************************** + */ + +$setup->insert($installer->getTable('eav/form_type'), array( + 'code' => 'checkout_onepage_billing_address', + 'label' => 'checkout_onepage_billing_address', + 'is_system' => 1, + 'theme' => '', + 'store_id' => 0 +)); +$formTypeId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $addressEntityTypeId +)); + +$elementSort = 0; +if ($showPrefix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'prefix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'firstname'), + 'sort_order' => $elementSort++ +)); +if ($showMiddlename) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'middlename'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'lastname'), + 'sort_order' => $elementSort++ +)); +if ($showSuffix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'suffix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'company'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'street'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'city'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'region'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'postcode'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'country_id'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'telephone'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'fax'), + 'sort_order' => $elementSort++ +)); + +/** + ***************************************************************************** + * checkout/onepage/shipping_address + ***************************************************************************** + */ + +$setup->insert($installer->getTable('eav/form_type'), array( + 'code' => 'checkout_onepage_shipping_address', + 'label' => 'checkout_onepage_shipping_address', + 'is_system' => 1, + 'theme' => '', + 'store_id' => 0 +)); +$formTypeId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $addressEntityTypeId +)); + +$elementSort = 0; +if ($showPrefix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'prefix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'firstname'), + 'sort_order' => $elementSort++ +)); +if ($showMiddlename) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'middlename'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'lastname'), + 'sort_order' => $elementSort++ +)); +if ($showSuffix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'suffix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'company'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'street'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'city'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'region'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'postcode'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'country_id'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'telephone'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => null, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'fax'), + 'sort_order' => $elementSort++ +)); + +/** + ***************************************************************************** + * checkout/multishipping/register/ + ***************************************************************************** + */ + +$setup->insert($installer->getTable('eav/form_type'), array( + 'code' => 'checkout_multishipping_register', + 'label' => 'checkout_multishipping_register', + 'is_system' => 1, + 'theme' => '', + 'store_id' => 0 +)); +$formTypeId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $customerEntityTypeId +)); +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $addressEntityTypeId +)); + +$setup->insert($installer->getTable('eav/form_fieldset'), array( + 'type_id' => $formTypeId, + 'code' => 'general', + 'sort_order' => 1 +)); +$fieldsetId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_fieldset_label'), array( + 'fieldset_id' => $fieldsetId, + 'store_id' => 0, + 'label' => 'Personal Information' +)); + +$elementSort = 0; +if ($showPrefix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'prefix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'firstname'), + 'sort_order' => $elementSort++ +)); +if ($showMiddlename) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'middlename'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'lastname'), + 'sort_order' => $elementSort++ +)); +if ($showSuffix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'suffix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'email'), + 'sort_order' => $elementSort++ +)); +if ($showDob) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'dob'), + 'sort_order' => $elementSort++ + )); +} +if ($showTaxVat) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($customerEntityTypeId, 'taxvat'), + 'sort_order' => $elementSort++ + )); +} + +$setup->insert($installer->getTable('eav/form_fieldset'), array( + 'type_id' => $formTypeId, + 'code' => 'address', + 'sort_order' => 2 +)); +$fieldsetId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_fieldset_label'), array( + 'fieldset_id' => $fieldsetId, + 'store_id' => 0, + 'label' => 'Address Information' +)); + +$elementSort = 0; +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'company'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'telephone'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'street'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'city'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'region'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'postcode'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($addressEntityTypeId, 'country_id'), + 'sort_order' => $elementSort++ +)); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Chronopay/etc/config.xml b/app/code/core/Mage/Chronopay/etc/config.xml index 667d5aae3e..abd08ed46e 100644 --- a/app/code/core/Mage/Chronopay/etc/config.xml +++ b/app/code/core/Mage/Chronopay/etc/config.xml @@ -50,20 +50,7 @@ Mage_Chronopay Mage_Chronopay_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - Mage_Chronopay_Block diff --git a/app/code/core/Mage/Cms/Block/Block.php b/app/code/core/Mage/Cms/Block/Block.php index 754b29304c..9a77a31d08 100644 --- a/app/code/core/Mage/Cms/Block/Block.php +++ b/app/code/core/Mage/Cms/Block/Block.php @@ -24,32 +24,34 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** - * Cms block content + * Cms block content block * * @category Mage * @package Mage_Cms - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Cms_Block_Block extends Mage_Core_Block_Abstract { + /** + * Prepare Content HTML + * + * @return string + */ protected function _toHtml() { - if (!$this->_beforeToHtml()) { - return ''; - } + $blockId = $this->getBlockId(); $html = ''; - if ($blockId = $this->getBlockId()) { + if ($blockId) { $block = Mage::getModel('cms/block') ->setStoreId(Mage::app()->getStore()->getId()) ->load($blockId); - if (!$block->getIsActive()) { - $html = ''; - } else { - $content = $block->getContent(); - - $processor = Mage::getModel('core/email_template_filter'); - $html = $processor->filter($content); + if ($block->getIsActive()) { + /* @var $helper Mage_Cms_Helper_Data */ + $helper = Mage::helper('cms'); + $processor = $helper->getBlockTemplateProcessor(); + $html = $processor->filter($block->getContent()); } } return $html; diff --git a/app/code/core/Mage/Cms/Block/Page.php b/app/code/core/Mage/Cms/Block/Page.php index 1620d07c14..fd417ad66f 100644 --- a/app/code/core/Mage/Cms/Block/Page.php +++ b/app/code/core/Mage/Cms/Block/Page.php @@ -24,15 +24,21 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** - * Cms page content + * Cms page content block * * @category Mage * @package Mage_Cms - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Cms_Block_Page extends Mage_Core_Block_Abstract { + /** + * Retrieve Page instance + * + * @return Mage_Cms_Model_Page + */ public function getPage() { if (!$this->hasData('page')) { @@ -48,6 +54,11 @@ public function getPage() return $this->getData('page'); } + /** + * Prepare global layout + * + * @return Mage_Cms_Block_Page + */ protected function _prepareLayout() { $page = $this->getPage(); @@ -61,20 +72,31 @@ protected function _prepareLayout() $breadcrumbs->addCrumb('cms_page', array('label'=>$page->getTitle(), 'title'=>$page->getTitle())); } - if ($root = $this->getLayout()->getBlock('root')) { + $root = $this->getLayout()->getBlock('root'); + if ($root) { $root->addBodyClass('cms-'.$page->getIdentifier()); } - if ($head = $this->getLayout()->getBlock('head')) { + $head = $this->getLayout()->getBlock('head'); + if ($head) { $head->setTitle($page->getTitle()); $head->setKeywords($page->getMetaKeywords()); $head->setDescription($page->getMetaDescription()); } + + return parent::_prepareLayout(); } + /** + * Prepare HTML content + * + * @return string + */ protected function _toHtml() { - $processor = Mage::getModel('core/email_template_filter'); + /* @var $helper Mage_Cms_Helper_Data */ + $helper = Mage::helper('cms'); + $processor = $helper->getPageTemplateProcessor(); $html = $processor->filter($this->getPage()->getContent()); $html = $this->getMessagesBlock()->getGroupedHtml() . $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 new file mode 100644 index 0000000000..340eef0fef --- /dev/null +++ b/app/code/core/Mage/Cms/Block/Widget/Block.php @@ -0,0 +1,60 @@ + + */ +class Mage_Cms_Block_Widget_Block extends Mage_Core_Block_Text implements Mage_Cms_Block_Widget_Interface +{ + /** + * Prepare block text and determine whether block output enabled or not + * + * @return bool + */ + protected function _beforeToHtml() + { + $blockId = $this->getData('block_id'); + if ($blockId) { + $block = Mage::getModel('cms/block') + ->setStoreId(Mage::app()->getStore()->getId()) + ->load($blockId); + if ($block->getIsActive()) { + /* @var $helper Mage_Cms_Helper_Data */ + $helper = Mage::helper('cms'); + $processor = $helper->getBlockTemplateProcessor(); + $html = $processor->filter($block->getContent()); + $this->setText($html); + return true; + } + } + return false; + } +} diff --git a/app/code/core/Mage/Cms/Block/Widget/Interface.php b/app/code/core/Mage/Cms/Block/Widget/Interface.php new file mode 100644 index 0000000000..90843fba70 --- /dev/null +++ b/app/code/core/Mage/Cms/Block/Widget/Interface.php @@ -0,0 +1,80 @@ + + */ +interface Mage_Cms_Block_Widget_Interface +{ + /** + * Produce and return widget's html output + * + * @return string + */ + public function toHtml(); + + /** + * Add data to the widget. + * Retains previous data in the widget. + * + * @param array $arr + * @return Mage_Cms_Block_Widget_Interface + */ + public function addData(array $arr); + + /** + * Overwrite data in the widget. + * + * $key can be string or array. + * If $key is string, the attribute value will be overwritten by $value. + * If $key is an array, it will overwrite all the data in the widget. + * + * @param string|array $key + * @param mixed $value + * @return Varien_Object + */ + public function setData($key, $value = null); + + /** + * Retrieve data from the widget. + * + * If $key is empty will return all the data as an array + * Otherwise it will return value of the attribute specified by $key + * + * If $index is specified it will assume that attribute data is an array + * and retrieve corresponding member. + * + * @param string $key + * @param string|int $index + * @return mixed + */ + public function getData($key = '', $index = null); +} diff --git a/app/code/core/Mage/Cms/Block/Widget/Page/Link.php b/app/code/core/Mage/Cms/Block/Widget/Page/Link.php new file mode 100644 index 0000000000..ec9fcfb354 --- /dev/null +++ b/app/code/core/Mage/Cms/Block/Widget/Page/Link.php @@ -0,0 +1,127 @@ + + */ + +class Mage_Cms_Block_Widget_Page_Link + extends Mage_Core_Block_Html_Link + implements Mage_Cms_Block_Widget_Interface +{ + /** + * Prepared href attribute + * + * @var string + */ + protected $_href; + + /** + * Prepared title attribute + * + * @var string + */ + protected $_title; + + /** + * Prepared anchor text + * + * @var string + */ + protected $_anchorText; + + /** + * Prepare page url. Use passed identifier + * or retrieve such using passed page id. + * + * @return string + */ + public function getHref() + { + if (!$this->_href) { + $this->_href = ''; + if ($this->getData('href')) { + $this->_href = $this->getData('href'); + } else if ($this->getData('page_id')) { + $href = Mage::getResourceSingleton('cms/page')->getCmsPageIdentifierById($this->getData('page_id')); + $this->_href = Mage::app()->getStore()->getUrl('', array('_direct' => $href)); + } + } + + return $this->_href; + } + + /** + * Prepare anchor title attribute using passed title + * as parameter or retrieve page title from DB using passed identifier or page id. + * + * @return string + */ + public function getTitle() + { + if (!$this->_title) { + $this->_title = ''; + if ($this->getData('title') !== null) { + // compare to null used here bc user can specify blank title + $this->_title = $this->getData('title'); + } else if ($this->getData('href')) { + $this->_title = Mage::getResourceSingleton('cms/page')->getCmsPageTitleByIdentifier($this->getData('href')); + } else if ($this->getData('page_id')) { + $this->_title = Mage::getResourceSingleton('cms/page')->getCmsPageTitleById($this->getData('page_id')); + } + } + + return $this->_title; + } + + /** + * Prepare anchor text using passed text as parameter. + * If anchor text was not specified use title instead and + * if title will be blank string, page identifier will be used. + * + * @return string + */ + public function getAnchorText() + { + if ($this->getData('anchor_text')) { + $this->_anchorText = $this->getData('anchor_text'); + } else if ($this->getTitle()) { + $this->_anchorText = $this->getTitle(); + } else if ($this->getData('href')) { + $this->_anchorText = Mage::getResourceSingleton('cms/page')->getCmsPageTitleByIdentifier($this->getData('href')); + } else if ($this->getData('page_id')) { + $this->_anchorText = Mage::getResourceSingleton('cms/page')->getCmsPageTitleById($this->getData('page_id')); + } else { + $this->_anchorText = $this->getData('href'); + } + + return $this->_anchorText; + } +} diff --git a/app/code/core/Mage/Cms/Controller/Router.php b/app/code/core/Mage/Cms/Controller/Router.php index 04a9b53fd5..83aa3f1f2f 100644 --- a/app/code/core/Mage/Cms/Controller/Router.php +++ b/app/code/core/Mage/Cms/Controller/Router.php @@ -41,6 +41,7 @@ class Mage_Cms_Controller_Router extends Mage_Core_Controller_Varien_Router_Abst */ public function initControllerRouters($observer) { + /* @var $front Mage_Core_Controller_Varien_Front */ $front = $observer->getEvent()->getFront(); $front->addRouter('cms', $this); @@ -63,6 +64,28 @@ public function match(Zend_Controller_Request_Http $request) $identifier = trim($request->getPathInfo(), '/'); + $condition = new Varien_Object(array( + 'identifier' => $identifier, + 'continue' => true + )); + Mage::dispatchEvent('cms_controller_router_match_before', array( + 'router' => $this, + 'condition' => $condition + )); + $identifier = $condition->getIdentifier(); + + if ($condition->getRedirectUrl()) { + Mage::app()->getFrontController()->getResponse() + ->setRedirect($condition->getRedirectUrl()) + ->sendResponse(); + $request->setDispatched(true); + return true; + } + + if (!$condition->getContinue()) { + return false; + } + $page = Mage::getModel('cms/page'); $pageId = $page->checkIdentifier($identifier, Mage::app()->getStore()->getId()); if (!$pageId) { diff --git a/app/code/core/Mage/Cms/Helper/Data.php b/app/code/core/Mage/Cms/Helper/Data.php index 4206212aa6..d68cb3490a 100644 --- a/app/code/core/Mage/Cms/Helper/Data.php +++ b/app/code/core/Mage/Cms/Helper/Data.php @@ -34,4 +34,28 @@ */ class Mage_Cms_Helper_Data extends Mage_Core_Helper_Abstract { + const XML_NODE_PAGE_TEMPLATE_FILTER = 'global/cms/page/tempate_filter'; + const XML_NODE_BLOCK_TEMPLATE_FILTER = 'global/cms/block/tempate_filter'; + + /** + * Retrieve Template processor for Page Content + * + * @return Varien_Filter_Template + */ + public function getPageTemplateProcessor() + { + $model = (string)Mage::getConfig()->getNode(self::XML_NODE_PAGE_TEMPLATE_FILTER); + return Mage::getModel($model); + } + + /** + * Retrieve Template processor for Block Content + * + * @return Varien_Filter_Template + */ + public function getBlockTemplateProcessor() + { + $model = (string)Mage::getConfig()->getNode(self::XML_NODE_BLOCK_TEMPLATE_FILTER); + return Mage::getModel($model); + } } diff --git a/app/code/core/Mage/Cms/Helper/Page.php b/app/code/core/Mage/Cms/Helper/Page.php index bba8f4c135..71e6da6733 100644 --- a/app/code/core/Mage/Cms/Helper/Page.php +++ b/app/code/core/Mage/Cms/Helper/Page.php @@ -39,7 +39,7 @@ class Mage_Cms_Helper_Page extends Mage_Core_Helper_Abstract const XML_PATH_HOME_PAGE = 'web/default/cms_home_page'; /** - * Renders CMS page + * Renders CMS page on front end * * Call from controller action * @@ -48,6 +48,19 @@ class Mage_Cms_Helper_Page extends Mage_Core_Helper_Abstract * @return boolean */ public function renderPage(Mage_Core_Controller_Front_Action $action, $pageId = null) + { + return $this->_renderPage($action, $pageId); + } + + /** + * Renders CMS page + * + * @param Mage_Core_Controller_Front_Action $action + * @param integer $pageId + * @param bool $renderLayout + * @return boolean + */ + 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()) { @@ -61,8 +74,10 @@ public function renderPage(Mage_Core_Controller_Front_Action $action, $pageId = return false; } + $inRange = Mage::app()->getLocale()->IsStoreDateInInterval(null, $page->getCustomThemeFrom(), $page->getCustomThemeTo()); + if ($page->getCustomTheme()) { - if (Mage::app()->getLocale()->IsStoreDateInInterval(null, $page->getCustomThemeFrom(), $page->getCustomThemeTo())) { + if ($inRange) { list($package, $theme) = explode('/', $page->getCustomTheme()); Mage::getSingleton('core/design_package') ->setPackageName($package) @@ -76,12 +91,15 @@ public function renderPage(Mage_Core_Controller_Front_Action $action, $pageId = $action->addActionLayoutHandles(); if ($page->getRootTemplate()) { - $action->getLayout()->helper('page/layout') - ->applyHandle($page->getRootTemplate()); + $handle = ($page->getCustomRootTemplate() + && $page->getCustomRootTemplate() != 'empty' + && $inRange) ? $page->getCustomRootTemplate() : $page->getRootTemplate(); + $action->getLayout()->helper('page/layout')->applyHandle($handle); } $action->loadLayoutUpdates(); - $action->getLayout()->getUpdate()->addUpdate($page->getLayoutUpdateXml()); + $layoutUpdate = ($page->getCustomLayoutUpdateXml() && $inRange) ? $page->getCustomLayoutUpdateXml() : $page->getLayoutUpdateXml(); + $action->getLayout()->getUpdate()->addUpdate($layoutUpdate); $action->generateLayoutXml()->generateLayoutBlocks(); if ($page->getRootTemplate()) { @@ -89,19 +107,35 @@ public function renderPage(Mage_Core_Controller_Front_Action $action, $pageId = ->applyTemplate($page->getRootTemplate()); } - if ($storage = Mage::getSingleton('catalog/session')) { - $action->getLayout()->getMessagesBlock()->addMessages($storage->getMessages(true)); + foreach (array('catalog/session', 'checkout/session') as $class_name) { + $storage = Mage::getSingleton($class_name); + if ($storage) { + $action->getLayout()->getMessagesBlock()->addMessages($storage->getMessages(true)); + } } - if ($storage = Mage::getSingleton('checkout/session')) { - $action->getLayout()->getMessagesBlock()->addMessages($storage->getMessages(true)); + if ($renderLayout) { + $action->renderLayout(); } - $action->renderLayout(); - return true; } + /** + * Renders CMS Page with more flexibility then original renderPage function. + * Allows to use also backend action as first parameter. + * Also takes third parameter which allows not run renderLayout method. + * + * @param Mage_Core_Controller_Varien_Action $action + * @param $pageId + * @param $renderLayout + * @return bool + */ + public function renderPageExtended(Mage_Core_Controller_Varien_Action $action, $pageId = null, $renderLayout = true) + { + return $this->_renderPage($action, $pageId, $renderLayout); + } + /** * Retrieve page direct URL * diff --git a/app/code/core/Mage/Cms/Helper/Wysiwyg/Images.php b/app/code/core/Mage/Cms/Helper/Wysiwyg/Images.php new file mode 100644 index 0000000000..b7362434ee --- /dev/null +++ b/app/code/core/Mage/Cms/Helper/Wysiwyg/Images.php @@ -0,0 +1,183 @@ +correctPath( $this->getStorage()->getConfigData('upload_root') ); + return Mage::getConfig()->getOptions()->getMediaDir() . DS . $root; + } + + /** + * Images Storage base URL + * + * @return string + */ + public function getBaseUrl() + { + $root = $this->correctPath( $this->getStorage()->getConfigData('upload_root') ); + return Mage::getBaseUrl('media') . $this->convertPathToUrl($root) . '/'; + } + + /** + * Ext Tree node key name + * + * @return string + */ + public function getTreeNodeName() + { + return 'node'; + } + + /** + * Encode path to HTML element id + * + * @param string $path Path to file/directory + * @return string + */ + public function convertPathToId($path) + { + $path = str_replace($this->getStorageRoot(), '', $path); + return Mage::helper('core')->urlEncode($path); + } + + /** + * Decode HTML element id + * + * @param string $id + * @return string + */ + public function convertIdToPath($id) + { + $path = Mage::helper('core')->urlDecode($id); + if (!strstr($path, $this->getStorageRoot())) { + $path = $this->getStorageRoot() . $path; + } + return $path; + } + + /** + * File system path correction + * + * @param string $path Original path + * @param boolean $trim Trim slashes or not + * @return string + */ + public function correctPath($path, $trim = true) + { + $path = strtr($path, "\\\/", DS . DS); + if ($trim) { + $path = trim($path, DS); + } + return $path; + } + + /** + * Return file system path as Url string + * + * @param string $path + * @return string + */ + public function convertPathToUrl($path) + { + return str_replace(DS, '/', $path); + } + + /** + * Return path of the current selected directory or root directory for startup + * Try to create target directory if it doesn't exist + * + * @throws Mage_Core_Exception + * @return string + */ + public function getCurrentPath() + { + if (!$this->_currentPath) { + $currentPath = $this->getStorageRoot(); + $path = $this->_getRequest()->getParam($this->getTreeNodeName()); + if ($path) { + $path = $this->convertIdToPath($path); + if (is_dir($path)) { + $currentPath = $path; + } + } + $io = new Varien_Io_File(); + if (!$io->isWriteable($currentPath) && !$io->mkdir($currentPath)) { + $message = Mage::helper('cms')->__('Directory %s is not writable by server',$currentPath); + Mage::throwException($message); + } + $this->_currentPath = $currentPath; + } + return $this->_currentPath; + } + + /** + * Return URL based on current selected directory or root directory for startup + * + * @return string + */ + public function getCurrentUrl() + { + if (!$this->_currentUrl) { + $path = str_replace(Mage::getConfig()->getOptions()->getMediaDir(), '', $this->getCurrentPath()); + $path = trim($path, DS); + $this->_currentUrl = Mage::getBaseUrl('media') . $this->convertPathToUrl($path) . '/'; + } + return $this->_currentUrl; + } + + /** + * Storage model singleton + * + * @return Mage_Cms_Model_Page_Wysiwyg_Images_Storage + */ + public function getStorage() + { + return Mage::getSingleton('cms/wysiwyg_images_storage'); + } +} diff --git a/app/code/core/Mage/Cms/Model/Config.php b/app/code/core/Mage/Cms/Model/Config.php new file mode 100644 index 0000000000..6b785d8ded --- /dev/null +++ b/app/code/core/Mage/Cms/Model/Config.php @@ -0,0 +1,73 @@ + + */ +class Mage_Cms_Model_Config +{ + /** + * Cms widgets installed in system. + * + * @var Varien_Simplexml_Element + */ + protected $_widgets; + + /** + * Retrieve widget by code + * + * @param string $code + * @return Varien_Simplexml_Element + */ + public function getWidget($code) + { + if ($code) { + return $this->getWidgets()->$code; + } + + return false; + } + + /** + * Retrieve widgets declared in system. + * + * @return Varien_Simplexml_Element + */ + public function getWidgets() + { + if (!$this->_widgets) { + $config = Mage::getConfig()->loadModulesConfiguration('widget.xml'); + $this->_widgets = $config->getNode('widgets'); + } + + return $this->_widgets; + } +} diff --git a/app/code/core/Mage/Cms/Model/Mysql4/Page.php b/app/code/core/Mage/Cms/Model/Mysql4/Page.php index 0c9e82f728..65791fd1ec 100644 --- a/app/code/core/Mage/Cms/Model/Mysql4/Page.php +++ b/app/code/core/Mage/Cms/Model/Mysql4/Page.php @@ -49,14 +49,14 @@ protected function _construct() */ protected function _beforeSave(Mage_Core_Model_Abstract $object) { - $format = Mage::app()->getLocale()->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT); + /* + * 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 ($date = $object->getData($dataKey)) { - $object->setData($dataKey, Mage::app()->getLocale()->date($date, $format, null, false) - ->toString(Varien_Date::DATETIME_INTERNAL_FORMAT) - ); - } - else { + if (!$object->getData($dataKey)) { $object->setData($dataKey, new Zend_Db_Expr('NULL')); } } @@ -177,8 +177,8 @@ public function getIsUniquePageToStores(Mage_Core_Model_Abstract $object) * Check whether page identifier is numeric * * @param Mage_Core_Model_Abstract $object - * @return bool - * @date Wed Mar 26 18:12:28 EET 2008 + * @return bool + * @date Wed Mar 26 18:12:28 EET 2008 */ protected function isNumericPageIdentifier (Mage_Core_Model_Abstract $object) { @@ -207,6 +207,51 @@ public function checkIdentifier($identifier, $storeId) 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 */ + $select->from(array('main_table' => $this->getMainTable()), 'title') + ->where('main_table.identifier = ?', $identifier); + 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 * @@ -220,4 +265,4 @@ public function lookupStoreIds($id) ->where("{$this->getIdFieldName()} = ?", $id) ); } -} \ No newline at end of file +} 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 8842dc29f3..e65250e494 100644 --- a/app/code/core/Mage/Cms/Model/Mysql4/Page/Collection.php +++ b/app/code/core/Mage/Cms/Model/Mysql4/Page/Collection.php @@ -39,6 +39,8 @@ class Mage_Cms_Model_Mysql4_Page_Collection extends Mage_Core_Model_Mysql4_Colle protected function _construct() { $this->_init('cms/page'); + + $this->_map['fields']['page_id'] = 'main_table.page_id'; } public function toOptionArray() @@ -91,18 +93,37 @@ protected function _afterLoad() */ public function addStoreFilter($store, $withAdmin = true) { - if ($store instanceof Mage_Core_Model_Store) { - $store = array($store->getId()); - } + 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->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/Widget.php b/app/code/core/Mage/Cms/Model/Mysql4/Widget.php new file mode 100644 index 0000000000..b4502175f0 --- /dev/null +++ b/app/code/core/Mage/Cms/Model/Mysql4/Widget.php @@ -0,0 +1,65 @@ + + */ + +class Mage_Cms_Model_Mysql4_Widget extends Mage_Core_Model_Mysql4_Abstract +{ + protected function _construct() + { + $this->_init('cms/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/Cms/Model/Page.php b/app/code/core/Mage/Cms/Model/Page.php index 3d5def7b47..ffc2e0fc7c 100644 --- a/app/code/core/Mage/Cms/Model/Page.php +++ b/app/code/core/Mage/Cms/Model/Page.php @@ -36,6 +36,12 @@ class Mage_Cms_Model_Page extends Mage_Core_Model_Abstract { const NOROUTE_PAGE_ID = 'no-route'; + /** + * Page's Statuses + */ + const STATUS_ENABLED = 1; + const STATUS_DISABLED = 0; + /** * Prefix of model events names * @@ -89,4 +95,22 @@ public function checkIdentifier($identifier, $storeId) { return $this->_getResource()->checkIdentifier($identifier, $storeId); } + + /** + * Prepare page's statuses. + * Available event cms_page_get_available_statuses to customize statuses. + * + * @return array + */ + public function getAvailableStatuses() + { + $statuses = new Varien_Object(array( + self::STATUS_ENABLED => Mage::helper('cms')->__('Enabled'), + self::STATUS_DISABLED => Mage::helper('cms')->__('Disabled'), + )); + + Mage::dispatchEvent('cms_page_get_available_statuses', array('statuses' => $statuses)); + + return $statuses->getData(); + } } diff --git a/app/code/core/Mage/Cms/Model/Template/Filter.php b/app/code/core/Mage/Cms/Model/Template/Filter.php new file mode 100644 index 0000000000..d059895f31 --- /dev/null +++ b/app/code/core/Mage/Cms/Model/Template/Filter.php @@ -0,0 +1,73 @@ + + */ +class Mage_Cms_Model_Template_Filter extends Mage_Core_Model_Email_Template_Filter +{ + /** + * Generate widget + * + * @param array $construction + * @return string + */ + public function widgetDirective($construction) + { + $params = $this->_getIncludeParameters($construction[2]); + + // Determine what name block should have in layout + $name = null; + if (isset($params['name'])) { + $name = $params['name']; + } + + // validate required parameter type or id + if (!empty($params['type'])) { + $type = $params['type']; + } elseif (!empty($params['id'])) { + $preconfigured = Mage::getResourceSingleton('cms/widget') + ->loadPreconfiguredWidget($params['id']); + $type = $preconfigured['type']; + $params = $preconfigured['parameters']; + } else { + return ''; + } + + // define widget block and check the type is instance of Widget Interface + $widget = Mage::app()->getLayout()->createBlock($type, $name, $params); + if (!$widget instanceof Mage_Cms_Block_Widget_Interface) { + return ''; + } + + return $widget->toHtml(); + } +} diff --git a/app/code/core/Mage/Cms/Model/Widget.php b/app/code/core/Mage/Cms/Model/Widget.php new file mode 100644 index 0000000000..1f153bed44 --- /dev/null +++ b/app/code/core/Mage/Cms/Model/Widget.php @@ -0,0 +1,146 @@ + + */ +class Mage_Cms_Model_Widget extends Varien_Object +{ + /** + * Load Widgets XML config from widget.xml files and cache it + * + * @return Varien_Simplexml_Config + */ + public function getXmlConfig() + { + $cachedXml = Mage::app()->loadCache('cms_widget_config'); + if ($cachedXml) { + $xmlConfig = new Varien_Simplexml_Config($cachedXml); + } else { + $config = new Varien_Simplexml_Config; + $config->loadString(''); + Mage::getConfig()->loadModulesConfiguration('widget.xml', $config); + $xmlConfig = $config; + if (Mage::app()->useCache('config')) { + Mage::app()->saveCache($config->getXmlString(), 'cms_widget_config', + array(Mage_Core_Model_Config::CACHE_TAG)); + } + } + return $xmlConfig; + } + + /** + * Return widget XML config element based on its type + * + * @param string $type Widget type + * @return Varien_Simplexml_Element + */ + public function getXmlElementByType($type) + { + $elements = $this->getXmlConfig()->getNode('widgets')->xpath('*[@type="' . $type . '"]'); + if (is_array($elements) && isset($elements[0]) && $elements[0] instanceof Varien_Simplexml_Element) { + return $elements[0]; + } + return null; + } + + /** + * Return widget presentation code in WYSIWYG editor + * + * @param string $type Widget Type + * @param array $params Pre-configured Widget Params + * @param bool $asIs Return result as widget directive(true) or as placeholder image(false) + * @return string Widget directive ready to parse + */ + public function getWidgetDeclaration($type, $params = array(), $asIs = true) + { + $widget = $this->getXmlElementByType($type); + + $directive = '{{widget type="' . $type . '"'; + foreach ($params as $name => $value) { + // Retrieve default option value if pre-configured + if (trim($value) == '' && $widget->parameters) { + $value = (string)$widget->parameters->{$name}->value; + } + if ($value) { + $directive .= sprintf(' %s="%s"', $name, $value); + } + } + $directive .= '}}'; + + if ($asIs) { + return $directive; + } + + $imageName = str_replace('/', '__', $type) . '.gif'; + if (is_file($this->getPlaceholderImagesBaseDir() . DS . $imageName)) { + $image = $this->getPlaceholderImagesBaseUrl() . $imageName; + } else { + $image = $this->getPlaceholderImagesBaseUrl() . 'default.gif'; + } + $html = sprintf('', + $this->_idEncode($directive), + $image, + Mage::helper('core')->urlEscape($directive) + ); + return $html; + } + + /** + * Return Widget placeholders images URL + * + * @return string + */ + public function getPlaceholderImagesBaseUrl() + { + return Mage::getDesign()->getSkinUrl('images/widget/'); + } + + /** + * Return Widget placeholders images dir + * + * @return string + */ + public function getPlaceholderImagesBaseDir() + { + return Mage::getDesign()->getSkinBaseDir() . DS . 'images' . DS . 'widget'; + } + + /** + * Encode string to valid HTML id element, based on base64 encoding + * + * @param string $string + * @return string + */ + protected function _idEncode($string) + { + return strtr(base64_encode($string), '+/=', ':_-'); + } +} diff --git a/app/code/core/Mage/Cms/Model/Wysiwyg/Config.php b/app/code/core/Mage/Cms/Model/Wysiwyg/Config.php new file mode 100644 index 0000000000..3683f887e5 --- /dev/null +++ b/app/code/core/Mage/Cms/Model/Wysiwyg/Config.php @@ -0,0 +1,160 @@ + + */ +class Mage_Cms_Model_Wysiwyg_Config extends Varien_Object +{ + /** + * Wysiwyg behaviour + */ + const WYSIWYG_ENABLED = 'enabled'; + const WYSIWYG_HIDDEN = 'hidden'; + const WYSIWYG_DISABLED = 'disabled'; + + /** + * Return Wysiwyg config as Varien_Object + * + * Config options description: + * + * enabled: Enabled Visual Editor or not + * hidden: Show Visual Editor on page load or not + * use_container: Wrap Editor contents into div or not + * no_display: Hide Editor container or not (related to use_container) + * translator: Helper to translate phrases in lib + * files_browser_*: Files Browser (media, images) settings + * encode_directives: Encode template directives with JS or not + * widget_window_*: Widget plugin insertion settings + * widget_image_url: Default image placeholder fot widget insertion + * + * @param $data Varien_Object constructor params to override default config values + * @return Varien_Object + */ + public function getConfig($data = array()) + { + $config = new Varien_Object(); + $config->setData(array( + 'enabled' => $this->isEnabled(), + 'hidden' => $this->isHidden(), + 'use_container' => false, + 'no_display' => false, + 'translator' => Mage::helper('cms'), + 'files_browser_window_url' => Mage::getSingleton('adminhtml/url')->getUrl('*/cms_wysiwyg_images/index'), + 'files_browser_window_width' => Mage::getStoreConfig('cms/wysiwyg/browser_window_width'), + 'files_browser_window_height' => Mage::getStoreConfig('cms/wysiwyg/browser_window_height'), + 'encode_directives' => true, + 'directives_url' => Mage::getSingleton('adminhtml/url')->getUrl('*/cms_wysiwyg/directive'), + 'widget_plugin_src' => Mage::getBaseUrl('js').'mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js', + 'widget_images_url' => Mage::getSingleton('cms/widget')->getPlaceholderImagesBaseUrl(), + 'widget_placeholders' => $this->getAvailablePlaceholderFilenames(), + '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', + )); + + $config->setData('directives_url_quoted', preg_quote($config->getData('directives_url'))); + + if (is_array($data)) { + $config->addData($data); + } + + if (!$config->hasData('widget_window_url')) { + $config->setData('widget_window_url', $this->getWidgetWindowUrl($config)); + } + if (!$config->hasData('widget_window_no_wysiwyg_url')) { + $config->setData('widget_window_no_wysiwyg_url', $this->getWidgetWindowUrl($config, false)); + } + + return $config; + } + + /** + * Return Widgets Insertion Plugin Window URL + * + * @param array $params URL params + * @return string + */ + public function getWidgetWindowUrl($config, $wysiwygMode = true) + { + $params = $wysiwygMode ? array() : array('no_wysiwyg' => true); + + if ($config->getData('skip_context_widgets')) { + $params['skip_context_widgets'] = 1; + } + + if ($config->hasData('skip_widgets')) { + $skipped = $config->getData('skip_widgets'); + $skipped = is_array($skipped) ? $skipped : array($skipped); + $skipped = implode(',', $skipped); + $params['skip_widgets'] = Mage::helper('core')->urlEncode($skipped); + } + + return Mage::getSingleton('adminhtml/url')->getUrl('*/cms_widget/index', $params); + } + + /** + * Return list of existing widget image placeholders + * + * @return array + */ + public function getAvailablePlaceholderFilenames() + { + $collection = new Varien_Data_Collection_Filesystem(); + $collection->addTargetDir(Mage::getSingleton('cms/widget')->getPlaceholderImagesBaseDir()) + ->setCollectDirs(false) + ->setCollectFiles(true) + ->setCollectRecursively(false); + $result = array(); + foreach ($collection as $file) { + $result[] = $file->getBasename(); + } + return $result; + } + + /** + * Check whether Wysiwyg is enabled or not + * + * @return bool + */ + public function isEnabled() + { + return in_array(Mage::getStoreConfig('cms/wysiwyg/enabled'), array(self::WYSIWYG_ENABLED, self::WYSIWYG_HIDDEN)); + } + + /** + * Check whether Wysiwyg is loaded on demand or not + * + * @return bool + */ + public function isHidden() + { + return Mage::getStoreConfig('cms/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 new file mode 100644 index 0000000000..c5aef204b5 --- /dev/null +++ b/app/code/core/Mage/Cms/Model/Wysiwyg/Images/Storage.php @@ -0,0 +1,230 @@ + + */ +class Mage_Cms_Model_Wysiwyg_Images_Storage extends Varien_Object +{ + const DIRECTORY_NAME_REGEXP = '/^[a-z0-9\-\_]+$/si'; + + /** + * Return one-level child directories for specified path + * + * @param string $path Parent directory path + * @return Varien_Data_Collection_Filesystem + */ + public function getDirsCollection($path) + { + $collection = $this->getCollection($path) + ->setCollectDirs(true) + ->setCollectFiles(false) + ->setCollectRecursively(false); + return $collection; + } + + /** + * Return files + * + * @param string $path Parent directory path + * @param string $type Type of storage, e.g. image, media etc. + * @return Varien_Data_Collection_Filesystem + */ + public function getFilesCollection($path, $type = null) + { + $collection = $this->getCollection($path) + ->setCollectDirs(false) + ->setCollectFiles(true) + ->setCollectRecursively(false) + ->setOrder('mtime', Varien_Data_Collection::SORT_ORDER_ASC); + + // Add files extension filter + if ($allowed = $this->getAllowedExtensions($type)) { + $collection->setFilesFilter('/\.(' . implode('|', $allowed). ')$/i'); + } + + return $collection; + } + + /** + * Storage collection + * + * @param string $path Path to the directory + * @return Varien_Data_Collection_Filesystem + */ + public function getCollection($path = null) + { + $collection = Mage::getModel('cms/wysiwyg_images_storage_collection'); + if ($path !== null) { + $collection->addTargetDir($path); + } + return $collection; + } + + /** + * Create new directory in storage + * + * @param string $name New directory name + * @param string $path Parent directory path + * @throws Mage_Core_Exception + * @return array New directory info + */ + public function createDirectory($name, $path) + { + if (!preg_match(self::DIRECTORY_NAME_REGEXP, $name)) { + Mage::throwException(Mage::helper('cms')->__('Invalid folder name. Please, use alphanumeric characters')); + } + if (!is_dir($path) || !is_writable($path)) { + $path = Mage::helper('cms/wysiwyg_images')->getStorageRoot(); + } + + $newPath = $path . DS . $name; + + if (file_exists($newPath)) { + Mage::throwException(Mage::helper('cms')->__('Such directory already exists. Try another folder name')); + } + + $io = new Varien_Io_File(); + if ($io->mkdir($newPath)) { + $result = array( + 'name' => $name, + 'path' => $newPath, + 'id' => Mage::helper('cms/wysiwyg_images')->convertPathToId($newPath) + ); + return $result; + } + Mage::throwException(Mage::helper('cms')->__('Cannot create new directory')); + } + + /** + * Recursively delete directory from storage + * + * @param string $path Target dir + * @return void + */ + public function deleteDirectory($path) + { + $io = new Varien_Io_File(); + if (!$io->rmdir($path, true)) { + Mage::throwException(Mage::helper('cms')->__('Cannot delete directory %s', $path)); + } + } + + /** + * Upload and resize new file + * + * @param string $targetPath Target directory + * @param string $type Type of storage, e.g. image, media etc. + * @throws Mage_Core_Exception + * @return array File info Array + */ + public function uploadFile($targetPath, $type = null) + { + $uploader = new Varien_File_Uploader('image'); + if ($allowed = $this->getAllowedExtensions($type)) { + $uploader->setAllowedExtensions($allowed); + } + $uploader->setAllowRenameFiles(true); + $uploader->setFilesDispersion(false); + $result = $uploader->save($targetPath); + + if (!$result) { + Mage::throwException( Mage::helper('cms')->__('Cannot upload file') ); + } + + // create thumbnail + $thumbsPath = $targetPath . DS . '.thumbs'; + $io = new Varien_Io_File(); + if ($io->isWriteable($thumbsPath)) { + $io->mkdir($thumbsPath); + } + $image = Varien_Image_Adapter::factory('GD2'); + $image->open($targetPath . DS . $uploader->getUploadedFileName()); + $width = $this->getConfigData('browser_resize_width'); + $height = $this->getConfigData('browser_resize_height'); + $image->resize($width, $height); + $image->save($thumbsPath . DS . $uploader->getUploadedFileName()); + + $result['cookie'] = array( + 'name' => session_name(), + 'value' => $this->getSession()->getSessionId(), + 'lifetime' => $this->getSession()->getCookieLifetime(), + 'path' => $this->getSession()->getCookiePath(), + 'domain' => $this->getSession()->getCookieDomain() + ); + + return $result; + } + + /** + * Storage session + * + * @return Mage_Adminhtml_Model_Session + */ + public function getSession() + { + return Mage::getSingleton('adminhtml/session'); + } + + /** + * Wysiwyg Config reader + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function getConfigData($key, $default=false) + { + if (!$this->hasData($key)) { + $value = Mage::getStoreConfig('cms/wysiwyg/'.$key); + if (is_null($value) || false===$value) { + $value = $default; + } + $this->setData($key, $value); + } + return $this->getData($key); + } + + /** + * Prepare allowed_extensions config settings + * + * @param string $type Type of storage, e.g. image, media etc. + * @return array Array of allowed file extensions + */ + public function getAllowedExtensions($type = null) + { + $configKey = is_null($type) ? 'browser_allowed_extensions' : 'browser_'.$type.'_allowed_extensions'; + if (preg_match_all('/[a-z0-9]+/si', strtolower($this->getConfigData($configKey)), $matches)) { + return $matches[0]; + } + return array(); + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000000..811c6ae62e --- /dev/null +++ b/app/code/core/Mage/Cms/Model/Wysiwyg/Images/Storage/Collection.php @@ -0,0 +1,50 @@ + + */ +class Mage_Cms_Model_Wysiwyg_Images_Storage_Collection extends Varien_Data_Collection_Filesystem +{ + public function __construct() + { + parent::__construct(); + $this->setPageSize(10000); + } + + protected function _generateRow($filename) + { + return array( + 'filename' => $filename, + 'basename' => basename($filename), + 'mtime' => filemtime($filename) + ); + } +} diff --git a/app/code/core/Mage/Cms/etc/adminhtml.xml b/app/code/core/Mage/Cms/etc/adminhtml.xml new file mode 100644 index 0000000000..3541c30cc7 --- /dev/null +++ b/app/code/core/Mage/Cms/etc/adminhtml.xml @@ -0,0 +1,99 @@ + + + + + + CMS + 70 + + + Manage Pages + adminhtml/cms_page + 0 + + + Static Blocks + adminhtml/cms_block + 10 + + + Poll Manager + adminhtml/poll + 20 + + + + + + + + + + CMS + 70 + + + Static Blocks + 0 + + + Manage Pages + 10 + + + Save Page + 0 + + + Delete Page + 10 + + + + + Poll Manager + 20 + + + + + + + + + Content Management + + + + + + + + + + diff --git a/app/code/core/Mage/Cms/etc/config.xml b/app/code/core/Mage/Cms/etc/config.xml index f072602b4f..48dc255974 100644 --- a/app/code/core/Mage/Cms/etc/config.xml +++ b/app/code/core/Mage/Cms/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.8 + 0.7.11 @@ -45,7 +45,6 @@ - singleton cms/observer noRoute @@ -54,7 +53,6 @@ - singleton cms/observer noCookies @@ -88,52 +86,13 @@ - - - CMS - 70 - - - Manage Pages - adminhtml/cms_page - - - Static Blocks - adminhtml/cms_block - - - Poll Manager - adminhtml/poll - - - - - - - - - - CMS - 70 - - - Static Blocks - 0 - - - Manage Pages - 10 - - - Poll Manager - 20 - - - - - - - + + + + cms.xml + + + @@ -156,6 +115,9 @@ cms_block_store + + cms_widget + @@ -164,20 +126,7 @@ Mage_Cms - - core_setup - - - - core_write - - - - - core_read - - Mage_Cms_Block @@ -186,13 +135,20 @@ - singleton Mage_Cms_Controller_Router initControllerRouters + + + cms/template_filter + + + cms/template_filter + + @@ -206,5 +162,18 @@ 1 + + + enabled + content/images + jpg,jpeg,png,gif + jpg,jpeg,png,gif + flv,swf,avi,mov + 1000 + 800 + 100 + 75 + + diff --git a/app/code/core/Mage/Cms/etc/system.xml b/app/code/core/Mage/Cms/etc/system.xml index 3694f1c0a0..79614b4e10 100644 --- a/app/code/core/Mage/Cms/etc/system.xml +++ b/app/code/core/Mage/Cms/etc/system.xml @@ -71,5 +71,44 @@ + + Content Management + general + text + 1001 + 1 + 1 + 1 + + + WYSIWYG Options + text + 100 + 1 + 1 + 1 + + + Enable WYSIWYG editor + select + adminhtml/system_config_source_cms_wysiwyg_enabled + 1 + 1 + 1 + 1 + + + Images Upload Folder + path within the media directory + text + 1 + 1 + 1 + 1 + + + + + \ No newline at end of file diff --git a/app/code/core/Mage/Cms/etc/widget.xml b/app/code/core/Mage/Cms/etc/widget.xml new file mode 100644 index 0000000000..170156ffac --- /dev/null +++ b/app/code/core/Mage/Cms/etc/widget.xml @@ -0,0 +1,73 @@ + + + + + + CMS Page Link + Link to a CMS Page + + + 1 + 1 + CMS Page + + label + adminhtml/cms_page_widget_chooser + + 10 + + + 1 + Anchor Text + If empty, the Page Title will be used + text + + + 1 + Anchor Title + text + + + + + CMS Static Block + Contents of a Static Block + + + 1 + 1 + Block + + label + adminhtml/cms_block_widget_chooser + + + + + + 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 new file mode 100644 index 0000000000..9ab07afd31 --- /dev/null +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.10-0.7.11.php @@ -0,0 +1,41 @@ +startSetup(); + +$pageTable = $installer->getTable('cms/page'); + +$installer->getConnection()->addColumn($pageTable, 'custom_root_template', + "VARCHAR(255) NOT NULL DEFAULT '' AFTER `custom_theme`"); + +$installer->getConnection()->addColumn($pageTable, 'custom_layout_update_xml', + 'TEXT NULL AFTER `custom_root_template`'); + +$installer->endSetup(); 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 new file mode 100644 index 0000000000..ca18265e7f --- /dev/null +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.8-0.7.9.php @@ -0,0 +1,42 @@ +getTable('cms/widget'); + +$installer->run(' +CREATE TABLE IF NOT EXISTS `' . $table . '` ( + `widget_id` int(10) unsigned NOT NULL auto_increment, + `code` varchar(255) NOT NULL, + `type` varchar(255) NOT NULL, + `parameters` text, + PRIMARY KEY (`widget_id`), + KEY `IDX_CODE` (`code`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT="CMS Preconfigured Widgets";'); + 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 new file mode 100644 index 0000000000..d3ee79d523 --- /dev/null +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.9-0.7.10.php @@ -0,0 +1,36 @@ +getTable('cms/page'); +$blockTable = $installer->getTable('cms/block'); + +$installer->getConnection()->modifyColumn($pageTable, 'content', 'MEDIUMTEXT'); +$installer->getConnection()->modifyColumn($blockTable, 'content', 'MEDIUMTEXT'); + diff --git a/app/code/core/Mage/Compiler/Block/Process.php b/app/code/core/Mage/Compiler/Block/Process.php index 15acf2d3eb..da34d9be53 100644 --- a/app/code/core/Mage/Compiler/Block/Process.php +++ b/app/code/core/Mage/Compiler/Block/Process.php @@ -98,7 +98,7 @@ protected function _prepareLayout() */ protected function getHeader() { - return Mage::helper('compiler')->__('Compilation (Beta)'); + return Mage::helper('compiler')->__('Compilation'); } public function getChangeStatusButtonHtml() diff --git a/app/code/core/Mage/Compiler/Model/Process.php b/app/code/core/Mage/Compiler/Model/Process.php index a7c195bd0b..5923947beb 100644 --- a/app/code/core/Mage/Compiler/Model/Process.php +++ b/app/code/core/Mage/Compiler/Model/Process.php @@ -76,10 +76,18 @@ protected function _getIncludePaths() { if (empty($this->_includePaths)) { $originalPath = Mage::registry('original_include_path'); - $path = str_replace($originalPath, '', get_include_path()); + /** + * Exclude current dirrectory include path + */ + if ($originalPath == '.') { + $path = get_include_path(); + } else { + $path = str_replace($originalPath, '', get_include_path()); + } + $this->_includePaths = explode(PS, $path); foreach ($this->_includePaths as $index => $path) { - if (empty($path)) { + if (empty($path) || $path == '.') { unset($this->_includePaths[$index]); } } diff --git a/app/code/core/Mage/Compiler/controllers/ProcessController.php b/app/code/core/Mage/Compiler/controllers/ProcessController.php index 073d2245b8..d8bfad537f 100644 --- a/app/code/core/Mage/Compiler/controllers/ProcessController.php +++ b/app/code/core/Mage/Compiler/controllers/ProcessController.php @@ -54,9 +54,6 @@ protected function _getCompiler() } public function indexAction() { - Mage::getSingleton('adminhtml/session')->addError( - Mage::helper('compiler')->__('Compiler module is now in Beta (not to be used in production environment)') - ); $this->loadLayout(); $this->_setActiveMenu('system/tools'); $this->renderLayout(); diff --git a/app/code/core/Mage/Compiler/etc/adminhtml.xml b/app/code/core/Mage/Compiler/etc/adminhtml.xml new file mode 100644 index 0000000000..7cdfc42849 --- /dev/null +++ b/app/code/core/Mage/Compiler/etc/adminhtml.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + Compilation + 0 + + + + + + + + + + + + + + + + + Compilation + 1000 + compiler/process + + + + + + + diff --git a/app/code/core/Mage/Compiler/etc/compilation.xml b/app/code/core/Mage/Compiler/etc/compilation.xml index 70a06004d0..0139877c9a 100644 --- a/app/code/core/Mage/Compiler/etc/compilation.xml +++ b/app/code/core/Mage/Compiler/etc/compilation.xml @@ -66,7 +66,7 @@ - + @@ -306,7 +306,6 @@ - @@ -399,4 +398,4 @@ - \ No newline at end of file + diff --git a/app/code/core/Mage/Compiler/etc/config.xml b/app/code/core/Mage/Compiler/etc/config.xml index 9783aed5be..1256d820b1 100644 --- a/app/code/core/Mage/Compiler/etc/config.xml +++ b/app/code/core/Mage/Compiler/etc/config.xml @@ -51,20 +51,7 @@ Mage_Compiler - - core_setup - - - - core_write - - - - - core_read - - @@ -79,26 +66,6 @@ - - - - - - - - - - Compilation - 0 - - - - - - - - - @@ -108,21 +75,6 @@ - - - - - - - Compilation (Beta) - 1000 - compiler/process - - - - - - diff --git a/app/code/core/Mage/Contacts/Model/System/Config/Backend/Links.php b/app/code/core/Mage/Contacts/Model/System/Config/Backend/Links.php new file mode 100644 index 0000000000..1844d20ae5 --- /dev/null +++ b/app/code/core/Mage/Contacts/Model/System/Config/Backend/Links.php @@ -0,0 +1,40 @@ + + + + + + + + + + + + + Contacts Section + + + + + + + + + + diff --git a/app/code/core/Mage/Contacts/etc/config.xml b/app/code/core/Mage/Contacts/etc/config.xml index 4a6c5e3381..30f8c26405 100644 --- a/app/code/core/Mage/Contacts/etc/config.xml +++ b/app/code/core/Mage/Contacts/etc/config.xml @@ -64,9 +64,6 @@ Mage_Contacts - - core_setup - @@ -90,25 +87,6 @@ - - - - - - - - - - Contacts Section - - - - - - - - - diff --git a/app/code/core/Mage/Contacts/etc/system.xml b/app/code/core/Mage/Contacts/etc/system.xml index bae27e2c44..25b15ae709 100644 --- a/app/code/core/Mage/Contacts/etc/system.xml +++ b/app/code/core/Mage/Contacts/etc/system.xml @@ -48,6 +48,7 @@ Enable Contact Us select adminhtml/system_config_source_yesno + contacts/system_config_backend_links 10 1 1 diff --git a/app/code/core/Mage/Core/Block/Abstract.php b/app/code/core/Mage/Core/Block/Abstract.php index 9289266ddc..19a8e60ed2 100644 --- a/app/code/core/Mage/Core/Block/Abstract.php +++ b/app/code/core/Mage/Core/Block/Abstract.php @@ -43,7 +43,7 @@ abstract class Mage_Core_Block_Abstract extends Varien_Object * * @var string */ - protected $_name; + protected $_nameInLayout; /** * Parent layout of the block @@ -256,19 +256,20 @@ public function setBlockAlias($alias) return $this; } - public function getName() - { - return $this->_name; - } - - public function setName($name) + /** + * Set block's name in layout and unsets previous link if such exists. + * + * @param $name + * @return Mage_Core_Block_Abstract + */ + public function setNameInLayout($name) { - if (!empty($this->_name) && $this->getLayout()) { + if (!empty($this->_nameInLayout) && $this->getLayout()) { $this->getLayout() - ->unsetBlock($this->_name) + ->unsetBlock($this->_nameInLayout) ->setBlock($name, $this); } - $this->_name = $name; + $this->_nameInLayout = $name; return $this; } @@ -954,9 +955,14 @@ public function jsQuoteEscape($data, $quote = '\'') return $this->helper('core')->jsQuoteEscape($data, $quote); } + /** + * Alias for getName method. + * + * @return string + */ public function getNameInLayout() { - return $this->_getData('name_in_layout'); + return $this->_nameInLayout; } public function countChildren() diff --git a/app/code/core/Mage/Core/Block/Html/Date.php b/app/code/core/Mage/Core/Block/Html/Date.php index de6e494423..7fc2cb4d2b 100644 --- a/app/code/core/Mage/Core/Block/Html/Date.php +++ b/app/code/core/Mage/Core/Block/Html/Date.php @@ -38,14 +38,15 @@ class Mage_Core_Block_Html_Date extends Mage_Core_Block_Template protected function _toHtml() { $html = 'getValue().'" class="'.$this->getClass().'" style="width:100px" '.$this->getExtraParams().'/> '; + $html .= 'value="'.$this->getValue().'" class="'.$this->getClass().'" '.$this->getExtraParams().'/> '; - $html .= 'getImage() . '" alt="' . $this->helper('core')->__('Select Date') . '" class="v-middle" '; $html .= 'title="' . $this->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 new file mode 100644 index 0000000000..8178e208f4 --- /dev/null +++ b/app/code/core/Mage/Core/Block/Html/Link.php @@ -0,0 +1,97 @@ + + */ +class Mage_Core_Block_Html_Link extends Mage_Core_Block_Abstract +{ + /** + * Prepare link attributes as serialized and formated string + * + * @return string + */ + public function getLinkAttributes() + { + $allow = array( + 'href', 'title', 'charset', 'name', 'hreflang', 'rel', 'rev', 'accesskey', 'shape', + 'coords', 'tabindex', 'onfocus', 'onblur', // %attrs + 'id', 'class', 'style', // %coreattrs + 'lang', 'dir', // %i18n + 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmousemove', + 'onmouseout', 'onkeypress', 'onkeydown', 'onkeyup' // %events + ); + + $attributes = array(); + foreach ($allow as $attribute) { + $value = $this->getDataUsingMethod($attribute); + if (!is_null($value)) { + $attributes[$attribute] = $this->htmlEscape($value); + } + } + + if (!empty($attributes)) { + return $this->serialize($attributes); + } + return ''; + } + + /** + * serialize attributes + * + * @param array $attributes + * @param string $valueSeparator + * @param string $fieldSeparator + * @param string $quote + * @return string + */ + public function serialize($attributes = array(), $valueSeparator='=', $fieldSeparator=' ', $quote='"') + { + $res = ''; + $data = array(); + + foreach ($attributes as $key => $value) { + $data[] = $key . $valueSeparator . $quote . $value . $quote; + } + $res = implode($fieldSeparator, $data); + return $res; + } + + /** + * Render block HTML + * + * @return string + */ + protected function _toHtml() + { + return 'getLinkAttributes() . '>' . $this->htmlEscape($this->getAnchorText()) . ''; + } +} diff --git a/app/code/core/Mage/Core/Block/Messages.php b/app/code/core/Mage/Core/Block/Messages.php index 732fd6020f..51657b7be0 100644 --- a/app/code/core/Mage/Core/Block/Messages.php +++ b/app/code/core/Mage/Core/Block/Messages.php @@ -40,13 +40,30 @@ class Mage_Core_Block_Messages extends Mage_Core_Block_Template */ protected $_messages; + /** + * Flag which require message text escape + * + * @var bool + */ + protected $_escapeMessageFlag = false; + public function _prepareLayout() { $this->addMessages(Mage::getSingleton('core/session')->getMessages(true)); - parent::_prepareLayout(); } + /** + * Set message escape flag + * @param bool $flag + * @return Mage_Core_Block_Messages + */ + public function setEscapeMessageFlag($flag) + { + $this->_escapeMessageFlag = $flag; + return $this; + } + /** * Set messages collection * @@ -59,6 +76,12 @@ public function setMessages(Mage_Core_Model_Message_Collection $messages) return $this; } + /** + * Add messages to display + * + * @param Mage_Core_Model_Message_Collection $messages + * @return Mage_Core_Block_Messages + */ public function addMessages(Mage_Core_Model_Message_Collection $messages) { foreach ($messages->getItems() as $message) { @@ -161,7 +184,9 @@ public function getHtml($type=null) { $html = ''; foreach ($this->getMessages($type) as $message) { - $html.= ''.$message->getText().''; + $html.= '' + . ($this->_escapeMessageFlag) ? $this->htmlEscape($message->getText()) : $message->getText() + . ''; } $html .= ''; return $html; @@ -192,7 +217,7 @@ public function getGroupedHtml() foreach ( $messages as $message ) { $html.= ''; - $html.= $message->getText(); + $html.= ($this->_escapeMessageFlag) ? $this->htmlEscape($message->getText()) : $message->getText(); $html.= ''; } $html .= ''; diff --git a/app/code/core/Mage/Core/Block/Template.php b/app/code/core/Mage/Core/Block/Template.php index 6421d66b1f..65086f8f0f 100644 --- a/app/code/core/Mage/Core/Block/Template.php +++ b/app/code/core/Mage/Core/Block/Template.php @@ -56,9 +56,53 @@ class Mage_Core_Block_Template extends Mage_Core_Block_Abstract protected static $_showTemplateHints; protected static $_showTemplateHintsBlocks; + /** + * Path to template file in theme. + * + * @var string + */ + protected $_template; + + /** + * Internal constructor, that is called from real constructor + * + */ + protected function _construct() + { + parent::_construct(); + + /* + * In case template was passed through constructor + * we assign it to block's property _template + * Mainly for those cases when block created + * not via Mage_Core_Model_Layout::addBlock() + */ + if ($this->hasData('template')) { + $this->setTemplate($this->getData('template')); + } + } + + /** + * Retrieve path to template used for generating block's output. + * + * @return string + */ public function getTemplate() { - return $this->_getData('template'); + return $this->_template; + } + + /** + * Set path to template used for generating block's output. + * + * @param string $template + * @return Mage_Core_Block_Template + */ + public function setTemplate($template) + { + $this->_template = $template; + + return $this; } public function getArea() diff --git a/app/code/core/Mage/Core/Controller/Front/Action.php b/app/code/core/Mage/Core/Controller/Front/Action.php index 81a36e3f7b..9d5aff916e 100644 --- a/app/code/core/Mage/Core/Controller/Front/Action.php +++ b/app/code/core/Mage/Core/Controller/Front/Action.php @@ -33,6 +33,20 @@ */ class Mage_Core_Controller_Front_Action extends Mage_Core_Controller_Varien_Action { + /** + * Currently used area + * + * @var string + */ + protected $_currentArea = 'frontend'; + + /** + * Namespace for session. + * + * @var string + */ + protected $_sessionNamespace = 'frontend'; + /** * Predispatch: shoud set layout area * @@ -40,7 +54,7 @@ class Mage_Core_Controller_Front_Action extends Mage_Core_Controller_Varien_Acti */ public function preDispatch() { - $this->getLayout()->setArea('frontend'); + $this->getLayout()->setArea($this->_currentArea); parent::preDispatch(); return $this; diff --git a/app/code/core/Mage/Core/Controller/Request/Http.php b/app/code/core/Mage/Core/Controller/Request/Http.php index 269151b7cf..ef29372fba 100644 --- a/app/code/core/Mage/Core/Controller/Request/Http.php +++ b/app/code/core/Mage/Core/Controller/Request/Http.php @@ -57,6 +57,13 @@ class Mage_Core_Controller_Request_Http extends Zend_Controller_Request_Http protected $_directFrontNames = array(); protected $_controllerModule = null; + /** + * Request's original information before forward. + * + * @var array + */ + protected $_beforeForwardInfo = array(); + public function __construct($uri = null) { parent::__construct($uri); @@ -391,4 +398,43 @@ public function getRequestedActionName() } return $this->getActionName(); } + + /** + * Collect properties changed by _forward in protected storage + * before _forward was called first time. + * + * @return Mage_Core_Controller_Varien_Action + */ + public function initForward() + { + if (empty($this->_beforeForwardInfo)) { + $this->_beforeForwardInfo = array( + 'params' => $this->getParams(), + 'action_name' => $this->getActionName(), + 'controller_name' => $this->getControllerName(), + 'module_name' => $this->getModuleName() + ); + } + + return $this; + } + + /** + * Retrieve property's value which was before _forward call. + * If property was not changed during _forward call null will be returned. + * If passed name will be null whole state array will be returned. + * + * @param string $name + * @return array|string|null + */ + public function getBeforeForwardInfo($name = null) + { + if (is_null($name)) { + return $this->_beforeForwardInfo; + } elseif (isset($this->_beforeForwardInfo[$name])) { + return $this->_beforeForwardInfo[$name]; + } + + return null; + } } \ No newline at end of file diff --git a/app/code/core/Mage/Core/Controller/Varien/Action.php b/app/code/core/Mage/Core/Controller/Varien/Action.php index e3ec66df0d..5b12422e7f 100644 --- a/app/code/core/Mage/Core/Controller/Varien/Action.php +++ b/app/code/core/Mage/Core/Controller/Varien/Action.php @@ -89,6 +89,21 @@ abstract class Mage_Core_Controller_Varien_Action */ protected $_cookieCheckActions = array(); + /** + * Currently used area + * + * @var string + */ + protected $_currentArea; + + /** + * Namespace for session. + * Should be defined for proper working session. + * + * @var string + */ + protected $_sessionNamespace; + /** * Constructor * @@ -432,14 +447,13 @@ public function preDispatch() } if (!$this->getFlag('', self::FLAG_NO_START_SESSION)) { - $namespace = $this->getLayout()->getArea(); $checkCookie = in_array($this->getRequest()->getActionName(), $this->_cookieCheckActions); $checkCookie = $checkCookie && !$this->getRequest()->getParam('nocookie', false); $cookies = Mage::getSingleton('core/cookie')->get(); if ($checkCookie && empty($cookies)) { $this->setFlag('', self::FLAG_NO_COOKIES_REDIRECT, true); } - Mage::getSingleton('core/session', array('name' => $namespace))->start(); + Mage::getSingleton('core/session', array('name' => $this->_sessionNamespace))->start(); } Mage::app()->loadArea($this->getLayout()->getArea()); @@ -530,10 +544,20 @@ public function noCookiesAction() $this->getRequest()->setDispatched(true); } + /** + * Throw control to different action (control and module if was specified). + * + * @param string $action + * @param string|null $controller + * @param string|null $module + * @param string|null $params + */ protected function _forward($action, $controller = null, $module = null, array $params = null) { $request = $this->getRequest(); + $request->initForward(); + if (!is_null($params)) { $request->setParams($params); } @@ -555,6 +579,9 @@ protected function _initLayoutMessages($messagesStorage) { if ($storage = Mage::getSingleton($messagesStorage)) { $this->getLayout()->getMessagesBlock()->addMessages($storage->getMessages(true)); + $this->getLayout()->getMessagesBlock()->setEscapeMessageFlag( + $storage->getEscapeMessages(true) + ); } else { Mage::throwException( @@ -577,7 +604,7 @@ protected function _redirectUrl($url) } /** - * Set redirect into responce + * Set redirect into response * * @param string $path * @param array $arguments 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 917dd09225..1eea173cd4 100644 --- a/app/code/core/Mage/Core/Controller/Varien/Router/Standard.php +++ b/app/code/core/Mage/Core/Controller/Varien/Router/Standard.php @@ -104,7 +104,7 @@ protected function _afterModuleMatch() public function match(Zend_Controller_Request_Http $request) { - //checkings before even try to findout that current module + //checking before even try to find out that current module //should use this router if (!$this->_beforeModuleMatch()) { return false; @@ -198,7 +198,7 @@ public function match(Zend_Controller_Request_Http $request) } // instantiate controller class - $controllerInstance = new $controllerClassName($request, $front->getResponse()); + $controllerInstance = Mage::getControllerInstance($controllerClassName, $request, $front->getResponse()); if (!$controllerInstance->hasAction($action)) { continue; @@ -222,7 +222,7 @@ public function match(Zend_Controller_Request_Http $request) } // instantiate controller class - $controllerInstance = Mage::getControllerInstance($request, $front->getResponse()); + $controllerInstance = Mage::getControllerInstance($controllerClassName, $request, $front->getResponse()); if (!$controllerInstance->hasAction($action)) { return false; @@ -251,7 +251,7 @@ public function match(Zend_Controller_Request_Http $request) } /** - * Allow to control if we need to enable norout functionality in current router + * Allow to control if we need to enable no route functionality in current router * * @return bool */ diff --git a/app/code/core/Mage/Core/Helper/String.php b/app/code/core/Mage/Core/Helper/String.php index 5a64ae7fb4..03780a5d2e 100644 --- a/app/code/core/Mage/Core/Helper/String.php +++ b/app/code/core/Mage/Core/Helper/String.php @@ -51,20 +51,20 @@ public function truncate($string, $length = 80, $etc = '...', &$remainder = '', return ''; } - $originalLength = iconv_strlen($string, self::ICONV_CHARSET); + $originalLength = $this->strlen($string); if ($originalLength > $length) { - $length -= iconv_strlen($etc, self::ICONV_CHARSET); + $length -= $this->strlen($etc); if ($length <= 0) { return ''; } $preparedString = $string; $preparedlength = $length; if (!$breakWords) { - $preparedString = preg_replace('/\s+?(\S+)?$/', '', iconv_substr($string, 0, $length + 1, self::ICONV_CHARSET)); - $preparedlength = iconv_strlen($preparedString, self::ICONV_CHARSET); + $preparedString = preg_replace('/\s+?(\S+)?$/', '', $this->substr($string, 0, $length + 1)); + $preparedlength = $this->strlen($preparedString); } - $remainder = iconv_substr($string, $preparedlength, $originalLength, self::ICONV_CHARSET); - return iconv_substr($preparedString, 0, $length, self::ICONV_CHARSET) . $etc; + $remainder = $this->substr($string, $preparedlength, $originalLength); + return $this->substr($preparedString, 0, $length) . $etc; } return $string; @@ -92,7 +92,7 @@ public function strlen($str) public function substr($str, $offset, $length = null) { if (is_null($length)) { - $length = iconv_strlen($str, self::ICONV_CHARSET) - $offset; + $length = $this->strlen($str) - $offset; } return iconv_substr($str, $offset, $length, self::ICONV_CHARSET); } @@ -112,10 +112,10 @@ public function splitInjection($str, $length = 50, $needle = '-', $insert = ' ') $newStr = ''; foreach ($str as $part) { if ($this->strlen($part) >= $length) { - $lastDelimetr = iconv_strpos(strrev($part), $needle, null, self::ICONV_CHARSET); + $lastDelimetr = iconv_strpos($this->strrev($part), $needle, null, self::ICONV_CHARSET); $tmpNewStr = ''; - $tmpNewStr = $this->substr(strrev($part), 0, $lastDelimetr) . $insert . $this->substr(strrev($part), $lastDelimetr); - $newStr .= strrev($tmpNewStr); + $tmpNewStr = $this->substr($this->strrev($part), 0, $lastDelimetr) . $insert . $this->substr($this->strrev($part), $lastDelimetr); + $newStr .= $this->strrev($tmpNewStr); } else { $newStr .= $part; } @@ -137,7 +137,7 @@ public function strrev($str) return $result; } for ($i = $strlen-1; $i >= 0; $i--) { - $result .= iconv_substr($str, $i, 1, self::ICONV_CHARSET); + $result .= $this->substr($str, $i, 1); } return $result; } @@ -169,7 +169,7 @@ public function str_split($str, $length = 1, $keepWords = false, $trim = false, // do a usual str_split, but safe for our encoding if ((!$keepWords) || ($length < 2)) { for ($offset = 0; $offset < $strlen; $offset += $length) { - $result[] = iconv_substr($str, $offset, $length, self::ICONV_CHARSET); + $result[] = $this->substr($str, $offset, $length); } } // split smartly, keeping words @@ -194,9 +194,9 @@ public function str_split($str, $length = 1, $keepWords = false, $trim = false, $spaceLen = 0; } else { - $currentLength = iconv_strlen($result[$i], self::ICONV_CHARSET); + $currentLength = $this->strlen($result[$i]); } - $partLength = iconv_strlen($part, self::ICONV_CHARSET); + $partLength = $this->strlen($part); // add part to current last element if (($currentLength + $spaceLen + $partLength) <= $length) { $result[$i] .= $space . $part; @@ -217,12 +217,12 @@ public function str_split($str, $length = 1, $keepWords = false, $trim = false, } // remove last element, if empty if ($count = count($result)) { - if (empty($result[$count - 1])) { + if ($result[$count - 1] === '') { unset($result[$count - 1]); } } // remove first element, if empty - if (isset($result[0]) && empty($result[0])) { + if (isset($result[0]) && $result[0] === '') { array_shift($result); } return $result; @@ -233,11 +233,11 @@ public function str_split($str, $length = 1, $keepWords = false, $trim = false, * * @param string $str The source string * @param bool $uniqueOnly Unique words only - * @param int $maxWordLenght Limit words count + * @param int $maxWordLength Limit words count * @param string $wordSeparatorRegexp * @return array */ - function splitWords($str, $uniqueOnly = false, $maxWordLenght = 0, $wordSeparatorRegexp = '\s') + function splitWords($str, $uniqueOnly = false, $maxWordLength = 0, $wordSeparatorRegexp = '\s') { $result = array(); $split = preg_split('#' . $wordSeparatorRegexp . '#si', $str, null, PREG_SPLIT_NO_EMPTY); @@ -249,8 +249,8 @@ function splitWords($str, $uniqueOnly = false, $maxWordLenght = 0, $wordSeparato $result[] = $word; } } - if ($maxWordLenght && count($result) > $maxWordLenght) { - $result = array_slice($result, 0, $maxWordLenght); + if ($maxWordLength && count($result) > $maxWordLength) { + $result = array_slice($result, 0, $maxWordLength); } return $result; } diff --git a/app/code/core/Mage/Core/Model/Abstract.php b/app/code/core/Mage/Core/Model/Abstract.php index 40e9e6084e..80e5b1d94f 100644 --- a/app/code/core/Mage/Core/Model/Abstract.php +++ b/app/code/core/Mage/Core/Model/Abstract.php @@ -89,6 +89,13 @@ abstract class Mage_Core_Model_Abstract extends Varien_Object */ protected $_dataSaveAllowed = true; + /** + * Flag which allow detect object state: is it new object (without id) or existing one (with id) + * + * @var bool + */ + protected $_isObjectNew = null; + /** * Standard model initialization * @@ -244,7 +251,14 @@ public function afterLoad() */ public function save() { + /** + * Direct deleted items to delete method + */ + if ($this->isDeleted()) { + return $this->delete(); + } $this->_getResource()->beginTransaction(); + $dataCommited = false; try { $this->_beforeSave(); if ($this->_dataSaveAllowed) { @@ -252,14 +266,49 @@ public function save() $this->_afterSave(); } $this->_getResource()->commit(); - } - catch (Exception $e){ + $dataCommited = true; + } catch (Exception $e) { $this->_getResource()->rollBack(); throw $e; } + if ($dataCommited) { + $this->_afterSaveCommit(); + } return $this; } + /** + * Processing data save after main transaction commit + * + * @return Mage_Core_Model_Abstract + */ + protected function _afterSaveCommit() + { + Mage::dispatchEvent('model_save_commit_after', array('object'=>$this)); + Mage::dispatchEvent($this->_eventPrefix.'_save_commit_after', array($this->_eventObject=>$this)); + return $this; + } + + /** + * Check object state (true - if it is object without id on object just created) + * This method can help detect if object just created in _afterSave method + * problem is what in after save onject has id and we can't detect what object was + * created in this transaction + * + * @param bool $flag + * @return bool + */ + public function isObjectNew($flag=null) + { + if ($flag !== null) { + $this->_isObjectNew = $flag; + } + if ($this->_isObjectNew !== null) { + return $this->_isObjectNew; + } + return !(bool)$this->getId(); + } + /** * Processing object before save data * @@ -267,6 +316,9 @@ public function save() */ protected function _beforeSave() { + if (!$this->getId()) { + $this->isObjectNew(true); + } Mage::dispatchEvent('model_save_before', array('object'=>$this)); Mage::dispatchEvent($this->_eventPrefix.'_save_before', array($this->_eventObject=>$this)); return $this; @@ -282,8 +334,9 @@ protected function _afterSave() if ($this->_cacheTag) { if ($this->_cacheTag === true) { $tags = array(); - } - else { + } elseif (is_array($this->_cacheTag)) { + $tags = $this->_cacheTag; + } else { $tags = array($this->_cacheTag); } Mage::app()->cleanCache($tags); @@ -307,6 +360,7 @@ public function delete() $this->_afterDelete(); $this->_getResource()->commit(); + $this->_afterDeleteCommit(); } catch (Exception $e){ $this->_getResource()->rollBack(); @@ -363,6 +417,18 @@ protected function _afterDelete() return $this; } + /** + * Processing manipulation after main transaction commit + * + * @return Mage_Core_Model_Abstract + */ + protected function _afterDeleteCommit() + { + Mage::dispatchEvent('model_delete_commit_after', array('object'=>$this)); + Mage::dispatchEvent($this->_eventPrefix.'_delete_commit_after', array($this->_eventObject=>$this)); + return $this; + } + /** * Retrieve model resource * diff --git a/app/code/core/Mage/Core/Model/App.php b/app/code/core/Mage/Core/Model/App.php index a4f9c4004e..d4a7d6ebee 100644 --- a/app/code/core/Mage/Core/Model/App.php +++ b/app/code/core/Mage/Core/Model/App.php @@ -1197,10 +1197,10 @@ public function dispatchEvent($eventName, $args) $observers = array(); foreach ($eventConfig->observers->children() as $obsName=>$obsConfig) { $observers[$obsName] = array( - 'type' => $obsConfig->type ? (string)$obsConfig->type : 'singleton', + 'type' => (string)$obsConfig->type, 'model' => $obsConfig->class ? (string)$obsConfig->class : $obsConfig->getClassName(), - 'method' => (string)$obsConfig->method, - 'args' => (array)$obsConfig->args, + 'method'=> (string)$obsConfig->method, + 'args' => (array)$obsConfig->args, ); } $events[$eventName]['observers'] = $observers; @@ -1218,17 +1218,16 @@ public function dispatchEvent($eventName, $args) $observer->setData(array('event'=>$event)); Varien_Profiler::start('OBSERVER: '.$obsName); switch ($obs['type']) { - case 'singleton': + case 'object': case 'model': $method = $obs['method']; $observer->addData($args); - $object = Mage::getSingleton($obs['model']); + $object = Mage::getModel($obs['model']); $object->$method($observer); break; - - case 'object': case 'model': + default: $method = $obs['method']; $observer->addData($args); - $object = Mage::getModel($obs['model']); + $object = Mage::getSingleton($obs['model']); $object->$method($observer); break; } diff --git a/app/code/core/Mage/Core/Model/Config.php b/app/code/core/Mage/Core/Model/Config.php index d23e252a1a..7c80eef899 100644 --- a/app/code/core/Mage/Core/Model/Config.php +++ b/app/code/core/Mage/Core/Model/Config.php @@ -310,7 +310,7 @@ protected function _canUseLocalModules() $disableLocalModules = false; } - if ($disableLocalModules) { + if ($disableLocalModules && !defined('COMPILER_INCLUDE_PATH')) { set_include_path( // excluded '/app/code/local' BP . DS . 'app' . DS . 'code' . DS . 'community' . PS . @@ -1141,10 +1141,12 @@ public function getResourceConnectionConfig($name) $config = $this->getResourceConfig($name); if ($config) { $conn = $config->connection; - if (!empty($conn->use)) { - return $this->getResourceConnectionConfig((string)$conn->use); - } else { - return $conn; + if ($conn) { + if (!empty($conn->use)) { + return $this->getResourceConnectionConfig((string)$conn->use); + } else { + return $conn; + } } } return false; diff --git a/app/code/core/Mage/Core/Model/Config/Data.php b/app/code/core/Mage/Core/Model/Config/Data.php index 2df0086840..b08369d787 100644 --- a/app/code/core/Mage/Core/Model/Config/Data.php +++ b/app/code/core/Mage/Core/Model/Config/Data.php @@ -33,6 +33,22 @@ */ class Mage_Core_Model_Config_Data extends Mage_Core_Model_Abstract { + const ENTITY = 'core_config_data'; + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'core_config_data'; + + /** + * Parameter name in event + * + * In observe method you can use $observer->getEvent()->getObject() in this case + * + * @var string + */ + protected $_eventObject = 'config_data'; /** * Varien model constructor diff --git a/app/code/core/Mage/Core/Model/Config/Element.php b/app/code/core/Mage/Core/Model/Config/Element.php index 0bf57663d3..b61339e0ed 100644 --- a/app/code/core/Mage/Core/Model/Config/Element.php +++ b/app/code/core/Mage/Core/Model/Config/Element.php @@ -41,20 +41,20 @@ class Mage_Core_Model_Config_Element extends Varien_Simplexml_Element * @param boolean $value * @return boolean */ - public function is($var, $value=true) + public function is($var, $value = true) { $flag = $this->$var; - if ($value===true) { + if ($value === true) { $flag = strtolower((string)$flag); - if (!empty($flag) && 'false'!==$flag && '0'!==$flag && 'off'!==$flag) { + if (!empty($flag) && 'false' !== $flag && 'off' !== $flag) { return true; } else { return false; } } - return !empty($flag) && (0===strcasecmp($value, (string)$flag)); + return !empty($flag) && (0 === strcasecmp($value, (string)$flag)); } /** diff --git a/app/code/core/Mage/Core/Model/Config/Options.php b/app/code/core/Mage/Core/Model/Config/Options.php index 766b11b620..84cf952648 100644 --- a/app/code/core/Mage/Core/Model/Config/Options.php +++ b/app/code/core/Mage/Core/Model/Config/Options.php @@ -68,7 +68,6 @@ protected function _construct() public function getDir($type) { - $this->_construct(); $method = 'get'.ucwords($type).'Dir'; $dir = $this->$method(); if (!$dir) { diff --git a/app/code/core/Mage/Core/Model/Cookie.php b/app/code/core/Mage/Core/Model/Cookie.php index 8129085435..14d311d0f2 100644 --- a/app/code/core/Mage/Core/Model/Cookie.php +++ b/app/code/core/Mage/Core/Model/Cookie.php @@ -100,13 +100,23 @@ protected function _getResponse() */ public function getDomain() { - $domain = Mage::getStoreConfig(self::XML_PATH_COOKIE_DOMAIN, $this->getStore()); + $domain = $this->getConfigDomain(); if (empty($domain)) { $domain = $this->_getRequest()->getHttpHost(); } return $domain; } + /** + * Retrieve Config Domain for cookie + * + * @return string + */ + public function getConfigDomain() + { + return (string)Mage::getStoreConfig(self::XML_PATH_COOKIE_DOMAIN, $this->getStore()); + } + /** * Retrieve Path for cookie * @@ -128,10 +138,9 @@ public function getPath() */ public function getLifetime() { - if (null !== $this->_lifetime) { + if (!is_null($this->_lifetime)) { $lifetime = $this->_lifetime; - } - else { + } else { $lifetime = Mage::getStoreConfig(self::XML_PATH_COOKIE_LIFETIME, $this->getStore()); } if (!is_numeric($lifetime)) { 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 954e47911b..fd920f911f 100644 --- a/app/code/core/Mage/Core/Model/Design/Source/Design.php +++ b/app/code/core/Mage/Core/Model/Design/Source/Design.php @@ -27,28 +27,62 @@ class Mage_Core_Model_Design_Source_Design extends Mage_Eav_Model_Entity_Attribute_Source_Abstract { + protected $_isFullLabel = false; + + /** + * Setter + * Add package name to label + * + * @param boolean $isFullLabel + * @return Mage_Core_Model_Design_Source_Design + */ + public function setIsFulllabel($isFullLabel) + { + $this->_isFullLabel = $isFullLabel; + return $this; + } + + /** + * Getter + * + * @return boolean + */ + public function getIsFullLabel() + { + return $this->_isFullLabel; + } + + /** + * Retrieve All Design Theme Options + * + * @param bool $withEmpty add empty (please select) values to result + * @return array + */ public function getAllOptions($withEmpty = true) { if (is_null($this->_options)) { $design = Mage::getModel('core/design_package')->getThemeList(); $options = array(); - foreach ($design as $package=>$themes){ - $packageOption = array('label'=>$package); + foreach ($design as $package => $themes){ + $packageOption = array('label' => $package); $themeOptions = array(); foreach ($themes as $theme) { - $themeOptions[] = array('label'=>$theme, 'value'=>$package . '/' . $theme); + $themeOptions[] = array( + 'label' => ($this->getIsFullLabel() ? $package . ' / ' : '') . $theme, + 'value' => $package . '/' . $theme + ); } - $packageOption['value'] = $themeOptions; - $options[] = $packageOption; } $this->_options = $options; } - $options = $this->_options; if ($withEmpty) { - array_unshift($options, array('value'=>'', 'label'=>Mage::helper('core')->__('-- Please Select --'))); + array_unshift($options, array( + 'value'=>'', + 'label'=>Mage::helper('core')->__('-- Please Select --')) + ); } return $options; } diff --git a/app/code/core/Mage/Core/Model/Email/Template.php b/app/code/core/Mage/Core/Model/Email/Template.php index 86776fbfc2..659ae977bf 100644 --- a/app/code/core/Mage/Core/Model/Email/Template.php +++ b/app/code/core/Mage/Core/Model/Email/Template.php @@ -54,7 +54,8 @@ class Mage_Core_Model_Email_Template extends Varien_Object * Configuration path for default email templates * */ - const XML_PATH_TEMPLATE_EMAIL = 'global/template/email'; + const XML_PATH_TEMPLATE_EMAIL = 'global/template/email'; + const XML_PATH_SENDING_SET_RETURN_PATH = 'system/smtp/set_return_path'; protected $_templateFilter; protected $_preprocessFlag = false; @@ -315,9 +316,9 @@ public function getInclude($template, array $variables) * @param array $variables template variables * @return boolean **/ - public function send($email, $name=null, array $variables = array()) + public function send($email, $name = null, array $variables = array()) { - if(!$this->isValidForSend()) { + if (!$this->isValidForSend()) { return false; } @@ -332,6 +333,11 @@ public function send($email, $name=null, array $variables = array()) ini_set('smtp_port', Mage::getStoreConfig('system/smtp/port')); $mail = $this->getMail(); + + if (Mage::getStoreConfigFlag(self::XML_PATH_SENDING_SET_RETURN_PATH)) { + $mail->setReturnPath($this->getSenderEmail()); + } + if (is_array($email)) { foreach ($email as $emailOne) { $mail->addTo($emailOne, $name); 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 952a929856..acc4599f52 100644 --- a/app/code/core/Mage/Core/Model/Email/Template/Filter.php +++ b/app/code/core/Mage/Core/Model/Email/Template/Filter.php @@ -113,16 +113,20 @@ public function blockDirective($construction) } elseif (isset($blockParameters['id'])) { $block = $layout->createBlock('cms/block'); if ($block) { - $block->setBlockId($blockParameters['id']) - ->setBlockParams($blockParameters); - foreach ($blockParameters as $k => $v) { - if (in_array($k, $skipParams)) { - continue; - } - $block->setDataUsingMethod($k, $v); + $block->setBlockId($blockParameters['id']); + } + } + + if ($block) { + $block->setBlockParams($blockParameters); + foreach ($blockParameters as $k => $v) { + if (in_array($k, $skipParams)) { + continue; } + $block->setDataUsingMethod($k, $v); } } + if (!$block) { return ''; } diff --git a/app/code/core/Mage/Core/Model/Layout.php b/app/code/core/Mage/Core/Model/Layout.php index 2f38aa4af6..9ee4bc554e 100644 --- a/app/code/core/Mage/Core/Model/Layout.php +++ b/app/code/core/Mage/Core/Model/Layout.php @@ -368,7 +368,7 @@ protected function _translateLayoutNode($node, &$args) $args[$arg] = Mage::helper((string)$node['module'])->__($args[$arg]); } else { - $args[$arg] = __($args[$arg]); + $args[$arg] = Mage::helper('core')->__($args[$arg]); } } } diff --git a/app/code/core/Mage/Core/Model/Layout/Update.php b/app/code/core/Mage/Core/Model/Layout/Update.php index f772ced03a..68abaddefc 100644 --- a/app/code/core/Mage/Core/Model/Layout/Update.php +++ b/app/code/core/Mage/Core/Model/Layout/Update.php @@ -243,71 +243,97 @@ public function asSimplexml() */ public function merge($handle) { - if (!$this->fetchPackageLayoutUpdates($handle) - && !$this->fetchDbLayoutUpdates($handle)) { - #$this->removeHandle($handle); + $packageUpdatesStatus = $this->fetchPackageLayoutUpdates($handle); + if (Mage::app()->isInstalled()) { + $this->fetchDbLayoutUpdates($handle); } +// if (!$this->fetchPackageLayoutUpdates($handle) +// && !$this->fetchDbLayoutUpdates($handle)) { +// #$this->removeHandle($handle); +// } return $this; } public function fetchFileLayoutUpdates() { + $storeId = Mage::app()->getStore()->getId(); $elementClass = $this->getElementClass(); - $design = Mage::getSingleton('core/design_package'); - $area = $design->getArea(); - $storeId = Mage::app()->getStore()->getId(); - $cacheKey = 'LAYOUT_'.$area.'_STORE'.$storeId.'_'.$design->getPackageName().'_'.$design->getTheme('layout'); -#echo "TEST:".$cacheKey; + $cacheKey = 'LAYOUT_'.$design->getArea().'_STORE'.$storeId.'_'.$design->getPackageName().'_'.$design->getTheme('layout'); $cacheTags = array('layout'); - if (Mage::app()->useCache('layout') && ($layoutStr = Mage::app()->loadCache($cacheKey))) { $this->_packageLayout = simplexml_load_string($layoutStr, $elementClass); } - if (empty($layoutStr)) { - $updatesRoot = Mage::app()->getConfig()->getNode($area.'/layout/updates'); - Mage::dispatchEvent('core_layout_update_updates_get_after', array('updates' => $updatesRoot)); - $updateFiles = array(); - foreach ($updatesRoot->children() as $updateNode) { - if ($updateNode->file) { - $module = $updateNode->getAttribute('module'); - if ($module && Mage::getStoreConfigFlag('advanced/modules_disable_output/' . $module)) { - continue; - } - $updateFiles[] = (string)$updateNode->file; - } - } - - // custom local layout updates file - load always last - $updateFiles[] = 'local.xml'; - - $layoutStr = ''; - #$layoutXml = new $elementClass(''); - foreach ($updateFiles as $file) { - $filename = $design->getLayoutFilename($file); - if (!is_readable($filename)) { - continue; - } - $fileStr = file_get_contents($filename); - $fileStr = str_replace($this->_subst['from'], $this->_subst['to'], $fileStr); - $fileXml = simplexml_load_string($fileStr, $elementClass); - if (!$fileXml instanceof SimpleXMLElement) { - continue; - } - $layoutStr .= $fileXml->innerXml(); - - #$layoutXml->appendChild($fileXml); - } - $layoutXml = simplexml_load_string(''.$layoutStr.'', $elementClass); - - $this->_packageLayout = $layoutXml; - + $this->_packageLayout = $this->getFileLayoutUpdatesXml( + $design->getArea(), + $design->getPackageName(), + $design->getTheme('layout'), + $storeId + ); if (Mage::app()->useCache('layout')) { Mage::app()->saveCache($this->_packageLayout->asXml(), $cacheKey, $cacheTags, null); } } + + +// $elementClass = $this->getElementClass(); +// +// $design = Mage::getSingleton('core/design_package'); +// $area = $design->getArea(); +// $storeId = Mage::app()->getStore()->getId(); +// $cacheKey = 'LAYOUT_'.$area.'_STORE'.$storeId.'_'.$design->getPackageName().'_'.$design->getTheme('layout'); +//#echo "TEST:".$cacheKey; +// $cacheTags = array('layout'); +// +// if (Mage::app()->useCache('layout') && ($layoutStr = Mage::app()->loadCache($cacheKey))) { +// $this->_packageLayout = simplexml_load_string($layoutStr, $elementClass); +// } +// +// if (empty($layoutStr)) { +// $updatesRoot = Mage::app()->getConfig()->getNode($area.'/layout/updates'); +// Mage::dispatchEvent('core_layout_update_updates_get_after', array('updates' => $updatesRoot)); +// $updateFiles = array(); +// foreach ($updatesRoot->children() as $updateNode) { +// if ($updateNode->file) { +// $module = $updateNode->getAttribute('module'); +// if ($module && Mage::getStoreConfigFlag('advanced/modules_disable_output/' . $module)) { +// continue; +// } +// $updateFiles[] = (string)$updateNode->file; +// } +// } +// +// // custom local layout updates file - load always last +// $updateFiles[] = 'local.xml'; +// +// $layoutStr = ''; +// #$layoutXml = new $elementClass(''); +// foreach ($updateFiles as $file) { +// $filename = $design->getLayoutFilename($file); +// if (!is_readable($filename)) { +// continue; +// } +// $fileStr = file_get_contents($filename); +// $fileStr = str_replace($this->_subst['from'], $this->_subst['to'], $fileStr); +// $fileXml = simplexml_load_string($fileStr, $elementClass); +// if (!$fileXml instanceof SimpleXMLElement) { +// continue; +// } +// $layoutStr .= $fileXml->innerXml(); +// +// #$layoutXml->appendChild($fileXml); +// } +// $layoutXml = simplexml_load_string(''.$layoutStr.'', $elementClass); +// +// $this->_packageLayout = $layoutXml; +// +// if (Mage::app()->useCache('layout')) { +// Mage::app()->saveCache($this->_packageLayout->asXml(), $cacheKey, $cacheTags, null); +// } +// } + return $this; } @@ -315,17 +341,14 @@ public function fetchPackageLayoutUpdates($handle) { $_profilerKey = 'layout/package_update: '.$handle; Varien_Profiler::start($_profilerKey); - if (empty($this->_packageLayout)) { $this->fetchFileLayoutUpdates(); } foreach ($this->_packageLayout->$handle as $updateXml) { #echo ''.$handle.':'.print_r($updateXml,1).''; $this->fetchRecursiveUpdates($updateXml); - $this->addUpdate($updateXml->innerXml()); } - Varien_Profiler::stop($_profilerKey); return true; @@ -335,22 +358,15 @@ public function fetchDbLayoutUpdates($handle) { $_profilerKey = 'layout/db_update: '.$handle; Varien_Profiler::start($_profilerKey); - - try { - $updateStr = Mage::getResourceModel('core/layout')->fetchUpdatesByHandle($handle); - if (!$updateStr) { - return false; - } - $updateStr = str_replace($this->_subst['from'], $this->_subst['to'], $updateStr); - $updateXml = simplexml_load_string($updateStr, $this->getElementClass()); - $this->fetchRecursiveUpdates($updateXml); - - $this->addUpdate($update); - } catch (PDOException $e) { - throw $e; - } catch (Exception $e) { - + $updateStr = Mage::getResourceModel('core/layout')->fetchUpdatesByHandle($handle); + if (!$updateStr) { + return false; } + $updateStr = '' . $updateStr . ''; + $updateStr = str_replace($this->_subst['from'], $this->_subst['to'], $updateStr); + $updateXml = simplexml_load_string($updateStr, $this->getElementClass()); + $this->fetchRecursiveUpdates($updateXml); + $this->addUpdate($updateXml->innerXml()); Varien_Profiler::stop($_profilerKey); return true; @@ -365,4 +381,55 @@ public function fetchRecursiveUpdates($updateXml) } return $this; } + + /** + * Collect and merge layout updates from file + * + * @param string $area + * @param string $package + * @param string $theme + * @param integer $storeId + * @return Mage_Core_Model_Layout_Element + */ + public function getFileLayoutUpdatesXml($area, $package, $theme, $storeId) + { + /* @var $design Mage_Core_Model_Design_Package */ + $design = Mage::getSingleton('core/design_package'); + $layoutXml = null; + $elementClass = $this->getElementClass(); + $updatesRoot = Mage::app()->getConfig()->getNode($area.'/layout/updates'); + Mage::dispatchEvent('core_layout_update_updates_get_after', array('updates' => $updatesRoot)); + $updateFiles = array(); + foreach ($updatesRoot->children() as $updateNode) { + if ($updateNode->file) { + $module = $updateNode->getAttribute('module'); + if ($module && Mage::getStoreConfigFlag('advanced/modules_disable_output/' . $module, $storeId)) { + continue; + } + $updateFiles[] = (string)$updateNode->file; + } + } + // custom local layout updates file - load always last + $updateFiles[] = 'local.xml'; + $layoutStr = ''; + foreach ($updateFiles as $file) { + $filename = $design->getLayoutFilename($file, array( + '_area' => $area, + '_package' => $package, + '_theme' => $theme + )); + if (!is_readable($filename)) { + continue; + } + $fileStr = file_get_contents($filename); + $fileStr = str_replace($this->_subst['from'], $this->_subst['to'], $fileStr); + $fileXml = simplexml_load_string($fileStr, $elementClass); + if (!$fileXml instanceof SimpleXMLElement) { + continue; + } + $layoutStr .= $fileXml->innerXml(); + } + $layoutXml = simplexml_load_string(''.$layoutStr.'', $elementClass); + return $layoutXml; + } } diff --git a/app/code/core/Mage/Core/Model/Locale.php b/app/code/core/Mage/Core/Model/Locale.php index 935b327475..9996482077 100644 --- a/app/code/core/Mage/Core/Model/Locale.php +++ b/app/code/core/Mage/Core/Model/Locale.php @@ -229,7 +229,7 @@ protected function _getOptionLocales($translatedName=false) { $options = array(); $locales = $this->getLocale()->getLocaleList(); - $languages = $this->getLocale()->getLanguageTranslationList($this->getLocale()); + $languages = $this->getLocale()->getTranslationList('language', $this->getLocale()); $countries = $this->getCountryTranslationList(); $allowed = $this->getAllowLocales(); @@ -243,8 +243,8 @@ protected function _getOptionLocales($translatedName=false) continue; } if ($translatedName) { - $label = ucwords($this->getLocale()->getLanguageTranslation($data[0], $code)) - . ' (' . $this->getLocale()->getCountryTranslation($data[1], $code) . ') / ' + $label = ucwords($this->getLocale()->getTranslation($data[0], 'language', $code)) + . ' (' . $this->getLocale()->getTranslation($data[1], 'country', $code) . ') / ' . $languages[$data[0]] . ' (' . $countries[$data[1]] . ')'; } else { $label = $languages[$data[0]] . ' (' . $countries[$data[1]] . ')'; @@ -511,6 +511,25 @@ public function storeDate($store=null, $date=null, $includeTime=false) return $date; } + /** + * Create Zend_Date object with date converted from store's timezone + * to UTC time zone. Date can be passed in format of store's locale + * or in format which was passed as parameter. + * + * @param mixed $store Information about store + * @param string|integer|Zend_Date|array|null $date date in store's timezone + * @param boolean $includeTime flag for including time to date + * @param null|string $format + * @return Zend_Date + */ + public function utcDate($store=null, $date, $includeTime = false, $format = null) + { + $dateObj = $this->storeDate($store, $date, $includeTime); + $dateObj->set($date, $format); + $dateObj->setTimezone(Mage_Core_Model_Locale::DEFAULT_TIMEZONE); + return $dateObj; + } + /** * Get store timestamp * Timstamp will be builded with store timezone settings @@ -529,19 +548,19 @@ public function storeTimeStamp($store=null) } /** - * Create Mage_Core_Model_Locale_Currency object for current locale + * Create Zend_Currency object for current locale * * @param string $currency - * @return Mage_Core_Model_Locale_Currency + * @return Zend_Currency */ public function currency($currency) { Varien_Profiler::start('locale/currency'); if (!isset(self::$_currencyCache[$this->getLocaleCode()][$currency])) { try { - $currencyObject = new Mage_Core_Model_Locale_Currency($currency, $this->getLocale()); + $currencyObject = new Zend_Currency($currency, $this->getLocale()); } catch (Exception $e) { - $currencyObject = new Mage_Core_Model_Locale_Currency($this->getCurrency(), $this->getLocale()); + $currencyObject = new Zend_Currency($this->getCurrency(), $this->getLocale()); $options = array( 'name' => $currency, 'currency' => $currency, @@ -724,7 +743,7 @@ public function getTranslation($value = null, $path = null) */ public function getCountryTranslation($value) { - return $this->getLocale()->getCountryTranslation($value, $this->getLocale()); + return $this->getLocale()->getTranslation($value, 'country', $this->getLocale()); } /** @@ -734,7 +753,7 @@ public function getCountryTranslation($value) */ public function getCountryTranslationList() { - return $this->getLocale()->getCountryTranslationList($this->getLocale()); + return $this->getLocale()->getTranslationList('territory', $this->getLocale(), 2); } /** diff --git a/app/code/core/Mage/Core/Model/Locale/Currency.php b/app/code/core/Mage/Core/Model/Locale/Currency.php deleted file mode 100644 index 3126c1e0e3..0000000000 --- a/app/code/core/Mage/Core/Model/Locale/Currency.php +++ /dev/null @@ -1,197 +0,0 @@ - - */ -class Mage_Core_Model_Locale_Currency extends Zend_Currency -{ - const XML_PATH_TRIM_CURRENCY_SIGN = 'currency/options/trim_sign'; - const US_LOCALE = 'en_US'; - protected $_locale; - - /** - * Creates a currency instance. Every supressed parameter is used from the actual or the given locale. - * - * @param string $currency OPTIONAL currency short name - * @param string|Zend_Locale $locale OPTIONAL locale name - * @throws Zend_Currency_Exception When currency is invalid - */ - public function __construct($currency = null, $locale = null) - { - parent::__construct($currency, $locale); - $this->_options['symbol_choice'] = self::getSymbolChoice($currency, $this->_locale); - } - - /** - * Returns the actual or details of available currency symbol choice, - * - * @param string $currency (Optional) Currency name - * @param string|Zend_Locale $locale (Optional) Locale to display informations - * @return string - */ - public function getSymbolChoice($currency = null, $locale = null) - { - if (($currency === null) and ($locale === null)) { - return $this->_options['symbol_choice']; - } - - $params = self::_checkParams($currency, $locale); - - //Get the symbol choice - $symbolChoice = Zend_Locale_Data::getContent($params['locale'], 'currencysymbolchoice', $params['currency']); - if (empty($symbolChoice) === true) { - $symbolChoice = Zend_Locale_Data::getContent($params['locale'], 'currencysymbolchoice', $params['name']); - } - if (empty($symbolChoice) === true) { - return null; - } - return $symbolChoice; - } - - public function setLocale($locale = null) - { - $this->_locale = $locale; - parent::setLocale($locale); - return $this; - } - - /** - * Place the sign next to the number - * - * @param string $value - * @param string $sign - * @param array $options - * @return string - */ - protected function _concatSign($value, $sign, $options) - { - $trimSign = $this->getStore()->getConfig(self::XML_PATH_TRIM_CURRENCY_SIGN); - if (is_null($trimSign) && $this->_locale && $this->_locale == self::US_LOCALE) { - $trimSign = true; - } - if ($trimSign) { - $sign = trim($sign); - } - - // Place the sign next to the number - if ($options['position'] === self::RIGHT) { - $result = $value . $sign; - } else if ($options['position'] === self::LEFT) { - // Do not place sign before minus. And do not allow space between minus and sign - if (0 === strpos($value, '-', 0)) { - $result = '-' . ltrim($sign) . substr($value, 1); - } else { - $result = $sign . $value; - } - } - return $result; - } - - /** - * Get store instance - * - * @return Mage_Core_Model_Store - */ - public function getStore() - { - return Mage::app()->getStore(); - } - -/** - * Internal method for checking the options array - * - * @param array $options - * @return array - * @throws Zend_Currency_Exception - */ - private function checkOptions(array $options = array()) - { - if (count($options) == 0) { - return $this->_options; - } - foreach($options as $name => $value) { - $name = strtolower($name); - if ($name !== 'format') { - if (gettype($value) === 'string') { - $value = strtolower($value); - } - } - if (array_key_exists($name, $this->_options)) { - switch($name) { - case 'position' : - if (($value !== self::STANDARD) and ($value !== self::RIGHT) and ($value !== self::LEFT)) { - #require_once 'Zend/Currency/Exception.php'; - throw new Zend_Currency_Exception("Unknown position '" . $value . "'"); - } - if ($value === self::STANDARD) { - $options['position'] = $this->_updateFormat(); - } - break; - case 'format' : - if (!empty($value) && (!Zend_Locale::isLocale($value))) { - #require_once 'Zend/Currency/Exception.php'; - throw new Zend_Currency_Exception("'" . - (gettype($value) === 'object' ? get_class($value) : $value) - . "' is not a known locale."); - } - break; - case 'display' : - if (is_numeric($value) and ($value !== self::NO_SYMBOL) and ($value !== self::USE_SYMBOL) and - ($value !== self::USE_SHORTNAME) and ($value !== self::USE_NAME)) { - #require_once 'Zend/Currency/Exception.php'; - throw new Zend_Currency_Exception("Unknown display '$value'"); - } - break; - case 'precision' : - if ($value === NULL) { - $value = -1; - } - if (($value < -1) || ($value > 30)) { - #require_once 'Zend/Currency/Exception.php'; - throw new Zend_Currency_Exception("'$value' precision has to be between -1 and 30."); - } - break; - case 'script' : - try { - Zend_Locale_Format::convertNumerals(0,$options['script']); - } catch (Zend_Locale_Exception $e) { - #require_once 'Zend/Currency/Exception.php'; - throw new Zend_Currency_Exception($e->getMessage()); - } - break; - } - } - else { - #require_once 'Zend/Currency/Exception.php'; - throw new Zend_Currency_Exception("Unknown option: '$name' = '$value'"); - } - } - return $options; - } -} diff --git a/app/code/core/Mage/Core/Model/Mysql4/Abstract.php b/app/code/core/Mage/Core/Model/Mysql4/Abstract.php index c2ed5e936c..aed549d3cd 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Abstract.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Abstract.php @@ -229,6 +229,7 @@ public function getMainTable() * Get table name for the entity * * @param string $entityName + * @return string */ public function getTable($entityName) { @@ -246,6 +247,18 @@ public function getTable($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 * @@ -270,7 +283,7 @@ protected function _getConnection($connectionName) /** * Retrieve connection for read data * - * @return Zend_Db_Adapter_Abstract + * @return Varien_Db_Adapter_Pdo_Mysql */ protected function _getReadAdapter() { @@ -280,7 +293,7 @@ protected function _getReadAdapter() /** * Retrieve connection for write data * - * @return Zend_Db_Adapter_Abstract + * @return Varien_Db_Adapter_Pdo_Mysql */ protected function _getWriteAdapter() { @@ -290,7 +303,7 @@ protected function _getWriteAdapter() /** * Temporary resolving collection compatibility * - * @return Zend_Db_Adapter_Abstract + * @return Varien_Db_Adapter_Pdo_Mysql */ public function getReadConnection() { @@ -364,7 +377,8 @@ public function save(Mage_Core_Model_Abstract $object) if ($this->_isPkAutoIncrement) { $this->_getWriteAdapter()->update($this->getMainTable(), $this->_prepareDataForSave($object), $condition); } else { - $select = $this->_getWriteAdapter()->select($this->getMainTable(), array($this->getIdFieldName())) + $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); @@ -482,9 +496,21 @@ public function getUniqueFields() * @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($this->getMainTable()); + $fields = $this->_getWriteAdapter()->describeTable($table); foreach (array_keys($fields) as $field) { if ($object->hasData($field)) { $fieldValue = $object->getData($field); @@ -567,10 +593,10 @@ protected function _checkUnique(Mage_Core_Model_Abstract $object) if (!empty($existent)) { if (count($existent) == 1 ) { - $error = Mage::helper('core')->__('%s already exist', $existent[0]); + $error = Mage::helper('core')->__('%s already exists', $existent[0]); } else { - $error = Mage::helper('core')->__('%s already exists', implode(', ', $existent)); + $error = Mage::helper('core')->__('%s already exist', implode(', ', $existent)); } Mage::throwException($error); } 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 d7d1808d2b..dbdc2fd38f 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Email/Template.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Email/Template.php @@ -189,7 +189,7 @@ protected function _prepareSave(Mage_Core_Model_Email_Template $template) $validators = array( 'template_code' => array(Zend_Filter_Input::ALLOW_EMPTY => false), - 'template_type' => 'Alnum', + #'template_type' => 'Alnum', #'template_sender_email' => 'EmailAddress', #'template_sender_name' => array(Zend_Filter_Input::ALLOW_EMPTY => false) ); diff --git a/app/code/core/Mage/Core/Model/Mysql4/Layout.php b/app/code/core/Mage/Core/Model/Mysql4/Layout.php index 9acb04c3d4..049ea8ec34 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Layout.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Layout.php @@ -44,22 +44,22 @@ public function fetchUpdatesByHandle($handle, $params = array()) $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'); - $read = $this->_getReadAdapter(); $updateStr = ''; - - if ($read) { - $select = $read->select()->from(array('update'=>$this->getMainTable()), 'xml') + + $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=?', $storeId) ->where('link.package=?', $package) - ->where('link.theme=?', $theme); - - if ($updates = $read->fetchAll($select)) { - foreach ($updates as $update) { - $updateStr .= $update['xml']; - } + ->where('link.theme=?', $theme) + ->where('update.handle = ?', $handle); + + foreach ($readAdapter->fetchAll($select) as $update) { + $updateStr .= $update['xml']; } } return $updateStr; } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Core/Model/Mysql4/Session.php b/app/code/core/Mage/Core/Model/Mysql4/Session.php index fa85569d98..af05d7a7a7 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Session.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Session.php @@ -62,6 +62,14 @@ class Mage_Core_Model_Mysql4_Session implements Zend_Session_SaveHandler_Interfa */ 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'); @@ -211,7 +219,12 @@ public function destroy($sessId) */ public function gc($sessMaxLifeTime) { - $this->_write->query("DELETE FROM `{$this->_sessionTable}` WHERE `session_expires` < ?", array(time())); + 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/Collection.php b/app/code/core/Mage/Core/Model/Mysql4/Store/Collection.php index 5e936bf107..c6ad7e0b60 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Store/Collection.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Store/Collection.php @@ -57,9 +57,21 @@ public function setWithoutDefaultFilter() 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) { - $condition = $this->getConnection()->quoteInto("main_table.group_id=?", $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; } 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 7db12ba479..b963717606 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Url/Rewrite.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Url/Rewrite.php @@ -81,4 +81,29 @@ protected function _getLoadSelect($field, $value, $object) return $select; } -} \ No newline at end of file + + /** + * 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/Website/Collection.php b/app/code/core/Mage/Core/Model/Mysql4/Website/Collection.php index 04983b9f61..3923ccabe9 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Website/Collection.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Website/Collection.php @@ -91,23 +91,50 @@ public function load($printQuery = false, $logQuery = false) 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() { - $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 - ; + 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/Resource.php b/app/code/core/Mage/Core/Model/Resource.php index 907491ffbd..d93a9ee277 100644 --- a/app/code/core/Mage/Core/Model/Resource.php +++ b/app/code/core/Mage/Core/Model/Resource.php @@ -37,6 +37,9 @@ class Mage_Core_Model_Resource const AUTO_UPDATE_NEVER = -1; const AUTO_UPDATE_ALWAYS = 1; + const DEFAULT_READ_RESOURCE = 'core_read'; + const DEFAULT_WRITE_RESOURCE= 'core_write'; + /** * Instances of classes for connection types * @@ -68,12 +71,16 @@ class Mage_Core_Model_Resource */ public function getConnection($name) { +// echo $name . ''; if (isset($this->_connections[$name])) { return $this->_connections[$name]; } $connConfig = Mage::getConfig()->getResourceConnectionConfig($name); - - if (!$connConfig || !$connConfig->is('active', 1)) { + if (!$connConfig) { + $this->_connections[$name] = $this->_getDefaultConnection($name); + return $this->_connections[$name]; + } + if (!$connConfig->is('active', 1)) { return false; } $origName = $connConfig->getParent()->getName(); @@ -93,6 +100,14 @@ public function getConnection($name) return $conn; } + protected function _getDefaultConnection($requiredConnectionName) + { + if (strpos($requiredConnectionName, 'read') !== false) { + return $this->getConnection(self::DEFAULT_READ_RESOURCE); + } + return $this->getConnection(self::DEFAULT_WRITE_RESOURCE); + } + /** * Get connection type instance * diff --git a/app/code/core/Mage/Core/Model/Resource/Setup.php b/app/code/core/Mage/Core/Model/Resource/Setup.php index 5498e9c9fe..b3b2e3902f 100644 --- a/app/code/core/Mage/Core/Model/Resource/Setup.php +++ b/app/code/core/Mage/Core/Model/Resource/Setup.php @@ -31,6 +31,7 @@ */ 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; @@ -56,7 +57,13 @@ public function __construct($resourceName) $config = Mage::getConfig(); $this->_resourceName = $resourceName; $this->_resourceConfig = $config->getResourceConfig($resourceName); - $this->_connectionConfig = $config->getResourceConnectionConfig($resourceName); + $connection = $config->getResourceConnectionConfig($resourceName); + if ($connection) { + $this->_connectionConfig = $connection; + } else { + $this->_connectionConfig = $config->getResourceConnectionConfig(self::DEFAULT_SETUP_CONNECTION); + } + $modName = (string)$this->_resourceConfig->setup->module; $this->_moduleConfig = $config->getModuleConfig($modName); $this->_conn = Mage::getSingleton('core/resource')->getConnection($this->_resourceName); 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 cad2ee993e..36961c55e2 100644 --- a/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php +++ b/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php @@ -45,12 +45,6 @@ public function start($sessionName=null) return $this; } - Varien_Profiler::start(__METHOD__.'/setOptions'); - if (is_writable(Mage::getBaseDir('session'))) { - session_save_path($this->getSessionSavePath()); - } - Varien_Profiler::stop(__METHOD__.'/setOptions'); - switch($this->getSessionSaveMethod()) { case 'db': ini_set('session.save_handler', 'user'); @@ -62,27 +56,49 @@ public function start($sessionName=null) ini_set('session.save_handler', 'memcache'); 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_save_path($this->getSessionSavePath()); + } break; } if (Mage::app()->getStore()->isAdmin()) { $adminSessionLifetime = (int)Mage::getStoreConfig('admin/security/session_cookie_lifetime'); if ($adminSessionLifetime > 60) { - Mage::getSingleton('core/cookie')->setLifetime($adminSessionLifetime); + $this->getCookie()->setLifetime($adminSessionLifetime); } } - // set session cookie params - session_set_cookie_params( - $this->getCookie()->getLifetime(), - $this->getCookie()->getPath(), - $this->getCookie()->getDomain(), - $this->getCookie()->isSecure(), - $this->getCookie()->getHttponly() + // session cookie params + $cookieParams = array( + 'lifetime' => $this->getCookie()->getLifetime(), + 'path' => $this->getCookie()->getPath(), + 'domain' => $this->getCookie()->getConfigDomain(), + 'secure' => $this->getCookie()->isSecure(), + 'httponly' => $this->getCookie()->getHttponly() ); + if (!$cookieParams['httponly']) { + unset($cookieParams['httponly']); + if (!$cookieParams['secure']) { + unset($cookieParams['secure']); + if (!$cookieParams['domain']) { + unset($cookieParams['domain']); + } + } + } + + if (isset($cookieParams['domain'])) { + $cookieParams['domain'] = $this->getCookie()->getDomain(); + } + + call_user_func_array('session_set_cookie_params', $cookieParams); + if (!empty($sessionName)) { $this->setSessionName($sessionName); } diff --git a/app/code/core/Mage/Core/Model/Store.php b/app/code/core/Mage/Core/Model/Store.php index 709bab5655..529969f4f4 100644 --- a/app/code/core/Mage/Core/Model/Store.php +++ b/app/code/core/Mage/Core/Model/Store.php @@ -28,12 +28,14 @@ /** * Store model * - * @author Magento Core Team + * @author Magento Core Team * @category Mage * @package Mage_Core */ class Mage_Core_Model_Store extends Mage_Core_Model_Abstract { + 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'; @@ -875,9 +877,18 @@ public function getName() return $this->_getData('name'); } + /** + * Protect delete from non admin area + * Register indexing event before delete store + * + * @return Mage_Core_Model_Store + */ protected function _beforeDelete() { $this->_protectFromNonAdmin(); + Mage::getSingleton('index/indexer')->logEvent( + $this, self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE + ); return parent::_beforeDelete(); } @@ -893,6 +904,19 @@ protected function _afterDelte() return $this; } + /** + * Init indexing process after store delete commit + * + * @return Mage_Core_Model_Store + */ + protected function _afterDeleteCommit() + { + parent::_afterDeleteCommit(); + Mage::getSingleton('index/indexer')->indexEvents( + self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE + ); + } + /** * Reinit and reset Config Data * diff --git a/app/code/core/Mage/Core/Model/Store/Group.php b/app/code/core/Mage/Core/Model/Store/Group.php index 213b9f12eb..fb91ce24f0 100644 --- a/app/code/core/Mage/Core/Model/Store/Group.php +++ b/app/code/core/Mage/Core/Model/Store/Group.php @@ -34,6 +34,7 @@ class Mage_Core_Model_Store_Group extends Mage_Core_Model_Abstract { + const ENTITY = 'store_group'; const CACHE_TAG = 'store_group'; protected $_cacheTag = true; diff --git a/app/code/core/Mage/Core/Model/Translate/Inline.php b/app/code/core/Mage/Core/Model/Translate/Inline.php index 52cd96b891..7bec912c31 100644 --- a/app/code/core/Mage/Core/Model/Translate/Inline.php +++ b/app/code/core/Mage/Core/Model/Translate/Inline.php @@ -168,7 +168,6 @@ public function processResponseBody(&$body) $this->processResponseBody($part); } } else if (is_string($body)) { - Mage::log($body); $this->_content = $body; $this->_tagAttributes(); diff --git a/app/code/core/Mage/Core/Model/Url/Rewrite.php b/app/code/core/Mage/Core/Model/Url/Rewrite.php index ea48def69e..b3af300f32 100644 --- a/app/code/core/Mage/Core/Model/Url/Rewrite.php +++ b/app/code/core/Mage/Core/Model/Url/Rewrite.php @@ -40,11 +40,34 @@ class Mage_Core_Model_Url_Rewrite extends Mage_Core_Model_Abstract const TYPE_CUSTOM = 3; const REWRITE_REQUEST_PATH_ALIAS = 'rewrite_request_path'; + /** + * Cache tag for clear cache in after save and after delete + * + * @var mixed | array | string | boolean + */ + protected $_cacheTag = false; + protected function _construct() { $this->_init('core/url_rewrite'); } + /** + * Clean cache for front-end menu + * + * @return Mage_Core_Model_Url_Rewrite + */ + protected function _afterSave() + { + if ($this->hasCategoryId()) { + $this->_cacheTag = array(Mage_Catalog_Model_Category::CACHE_TAG, Mage_Core_Model_Store_Group::CACHE_TAG); + } + + parent::_afterSave(); + + return $this; + } + /** * Load rewrite information for request * diff --git a/app/code/core/Mage/Core/Model/Website.php b/app/code/core/Mage/Core/Model/Website.php index 56772cb00a..d64570bce4 100644 --- a/app/code/core/Mage/Core/Model/Website.php +++ b/app/code/core/Mage/Core/Model/Website.php @@ -34,6 +34,7 @@ class Mage_Core_Model_Website extends Mage_Core_Model_Abstract { + const ENTITY = 'core_website'; const CACHE_TAG = 'website'; protected $_cacheTag = true; diff --git a/app/code/core/Mage/Core/etc/system.xml b/app/code/core/Mage/Core/etc/system.xml index 0843279ab2..81c43ac546 100644 --- a/app/code/core/Mage/Core/etc/system.xml +++ b/app/code/core/Mage/Core/etc/system.xml @@ -586,15 +586,6 @@ 1 1 - - Make Zip Code optional for Specific countries - multiselect - 3 - adminhtml/system_config_source_country - 1 - 1 - 0 - @@ -656,7 +647,7 @@ 1 - SMTP settings (Windows server only) + Mail Sending Settings text 20 1 @@ -679,6 +670,7 @@ 1 1 1 + For Windows Server Only Port (25) @@ -687,6 +679,7 @@ 1 1 1 + For Windows Server Only + + Set Return-Path + select + adminhtml/system_config_source_yesno + 70 + 1 + 0 + 0 + @@ -825,7 +827,7 @@ Session Lifetime (seconds) - Values less than 60 are ignored. + Values less than 60 are ignored. Please note, that changes will apply after logout. 3 1 0 @@ -1058,19 +1060,11 @@ Cookie Lifetime text - 30 - 1 - 1 - 1 - - - Cookie Domain - text 10 1 1 1 - + Cookie Path text @@ -1079,6 +1073,14 @@ 1 1 + + Cookie Domain + text + 30 + 1 + 1 + 1 + Use HTTP Only select diff --git a/app/code/core/Mage/Core/functions.php b/app/code/core/Mage/Core/functions.php index 9541627c7a..1c50b92430 100644 --- a/app/code/core/Mage/Core/functions.php +++ b/app/code/core/Mage/Core/functions.php @@ -180,13 +180,13 @@ function mageCoreErrorHandler($errno, $errstr, $errfile, $errline){ // PEAR specific message handling if (stripos($errfile.$errstr, 'pear') !== false) { - // ignore strict notices - if ($errno == E_STRICT) { - return false; + // ignore strict and deprecated notices + if (($errno == E_STRICT) || ($errno == E_DEPRECATED)) { + return true; } // ignore attempts to read system files when open_basedir is set if ($errno == E_WARNING && stripos($errstr, 'open_basedir') !== false) { - return false; + return true; } } @@ -371,4 +371,4 @@ function sys_get_temp_dir() } } } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Cron/etc/config.xml b/app/code/core/Mage/Cron/etc/config.xml index fa5fc6caff..8fa3ad147e 100644 --- a/app/code/core/Mage/Cron/etc/config.xml +++ b/app/code/core/Mage/Cron/etc/config.xml @@ -37,20 +37,7 @@ Mage_Cron - - core_setup - - - - core_write - - - - - core_read - - @@ -73,7 +60,6 @@ - singleton cron/observer dispatch diff --git a/app/code/core/Mage/Customer/Block/Widget/Gender.php b/app/code/core/Mage/Customer/Block/Widget/Gender.php new file mode 100644 index 0000000000..c12043c1f4 --- /dev/null +++ b/app/code/core/Mage/Customer/Block/Widget/Gender.php @@ -0,0 +1,74 @@ + + */ +class Mage_Customer_Block_Widget_Gender extends Mage_Customer_Block_Widget_Abstract +{ + /** + * Initialize block + */ + public function _construct() + { + parent::_construct(); + $this->setTemplate('customer/widget/gender.phtml'); + } + + /** + * Check if gender attribute enabled in system + * + * @return bool + */ + public function isEnabled() + { + return (bool)$this->getConfig('gender_show'); + } + + /** + * Check if gender attribute marked as required + * + * @return bool + */ + public function isRequired() + { + return 'req' == $this->getConfig('gender_show'); + } + + /** + * Get current customer from session + * + * @return Mage_Customer_Model_Customer + */ + public function getCustomer() + { + return Mage::getSingleton('customer/session')->getCustomer(); + } +} diff --git a/app/code/core/Mage/Customer/Model/Attribute.php b/app/code/core/Mage/Customer/Model/Attribute.php new file mode 100644 index 0000000000..5ea309f6ea --- /dev/null +++ b/app/code/core/Mage/Customer/Model/Attribute.php @@ -0,0 +1,56 @@ + + */ +class Mage_Customer_Model_Attribute extends Mage_Eav_Model_Entity_Attribute +{ + const MODULE_NAME = 'Mage_Customer'; + + protected $_eventPrefix = 'customer_entity_attribute'; + protected $_eventObject = 'attribute'; + + protected function _construct() + { + $this->_init('customer/attribute'); + } + + /** + * Processing object after save data + * + * @return Mage_Core_Model_Abstract + */ + protected function _afterSave() + { + Mage::getSingleton('eav/config')->clear(); + return parent::_afterSave(); + } +} diff --git a/app/code/core/Mage/Customer/Model/Convert/Adapter/Customer.php b/app/code/core/Mage/Customer/Model/Convert/Adapter/Customer.php index e5280238fa..3ad8246f15 100644 --- a/app/code/core/Mage/Customer/Model/Convert/Adapter/Customer.php +++ b/app/code/core/Mage/Customer/Model/Convert/Adapter/Customer.php @@ -199,7 +199,7 @@ public function getRegionId($country, $regionName) * * @return array */ - public function getCustomerGoups() + public function getCustomerGroups() { if (is_null($this->_customerGroups)) { $this->_customerGroups = array(); @@ -213,7 +213,15 @@ public function getCustomerGoups() return $this->_customerGroups; } - + /** + * Alias at getCustomerGroups() + * + * @return array + */ + public function getCustomerGoups() + { + return $this->getCustomerGroups(); + } public function __construct() { @@ -433,7 +441,7 @@ public function saveRow($importData) $customer->setWebsiteId($website->getId()) ->loadByEmail($importData['email']); if (!$customer->getId()) { - $customerGroups = $this->getCustomerGoups(); + $customerGroups = $this->getCustomerGroups(); /** * Check customer group */ @@ -465,7 +473,7 @@ public function saveRow($importData) } } elseif (!empty($importData['group_id'])) { - $customerGroups = $this->getCustomerGoups(); + $customerGroups = $this->getCustomerGroups(); /** * Check customer group */ diff --git a/app/code/core/Mage/Customer/Model/Convert/Parser/Customer.php b/app/code/core/Mage/Customer/Model/Convert/Parser/Customer.php index b3fa4f5d11..3e04c3539e 100644 --- a/app/code/core/Mage/Customer/Model/Convert/Parser/Customer.php +++ b/app/code/core/Mage/Customer/Model/Convert/Parser/Customer.php @@ -316,11 +316,19 @@ public function unparse() $row['created_in'] = $store->getCode(); $newsletter = $this->getNewsletterModel() + ->setData(array()) ->loadByCustomer($customer); $row['is_subscribed'] = ($newsletter->getId() && $newsletter->getSubscriberStatus() == Mage_Newsletter_Model_Subscriber::STATUS_SUBSCRIBED) ? 1 : 0; + if($customer->getGroupId()){ + $group = Mage::getResourceModel('customer/group_collection') + ->addFilter('customer_group_id',$customer->getGroupId()) + ->load(); + $row['group'] = $group->getFirstItem()->getCustomerGroupCode(); + } + $batchExport = $this->getBatchExportModel() ->setId(null) ->setBatchId($this->getBatchModel()->getId()) @@ -345,14 +353,10 @@ public function getExternalAttributes() 'country_id' ); - $entityTypeId = Mage::getSingleton('eav/config')->getEntityType('customer')->getId(); - $customerAttributes = Mage::getResourceModel('eav/entity_attribute_collection') - ->setEntityTypeFilter($entityTypeId) + $customerAttributes = Mage::getResourceModel('customer/attribute_collection') ->load()->getIterator(); - $entityTypeId = Mage::getSingleton('eav/config')->getEntityType('customer_address')->getId(); - $addressAttributes = Mage::getResourceModel('eav/entity_attribute_collection') - ->setEntityTypeFilter($entityTypeId) + $addressAttributes = Mage::getResourceModel('customer/address_attribute_collection') ->load()->getIterator(); $attributes = array( diff --git a/app/code/core/Mage/Customer/Model/Customer.php b/app/code/core/Mage/Customer/Model/Customer.php index f02b3f45e5..c84d77db80 100644 --- a/app/code/core/Mage/Customer/Model/Customer.php +++ b/app/code/core/Mage/Customer/Model/Customer.php @@ -450,7 +450,7 @@ public function isAddressPrimary(Mage_Customer_Model_Address $address) * * @return Mage_Customer_Model_Customer */ - public function sendNewAccountEmail($type = 'registered', $backUrl = '', $store_id = '0') + public function sendNewAccountEmail($type = 'registered', $backUrl = '', $storeId = '0') { $types = array( 'registered' => self::XML_PATH_REGISTER_EMAIL_TEMPLATE, // welcome email, when confirmation is disabled @@ -465,7 +465,7 @@ public function sendNewAccountEmail($type = 'registered', $backUrl = '', $store_ /* @var $translate Mage_Core_Model_Translate */ $translate->setTranslateInline(false); - $storeId = ($store_id == '0')?$this->getStoreId():$store_id; + $storeId = ($storeId == '0')?$this->getSendemailStoreId():$storeId; if ($this->getWebsiteId() != '0' && $storeId == '0') { $storeIds = Mage::app()->getWebsite($this->getWebsiteId())->getStoreIds(); reset($storeIds); @@ -695,6 +695,10 @@ public function validate() && '' == trim($this->getTaxvat())) { $errors[] = Mage::helper('customer')->__('TAX/VAT number is required.'); } + if (('req' === Mage::helper('customer/address')->getConfig('gender_show')) + && '' == trim($this->getGender())) { + $errors[] = Mage::helper('customer')->__('Gender is required.'); + } if (empty($errors)) { return true; diff --git a/app/code/core/Mage/Customer/Model/Entity/Address/Attribute/Collection.php b/app/code/core/Mage/Customer/Model/Entity/Address/Attribute/Collection.php new file mode 100644 index 0000000000..9b0743e89f --- /dev/null +++ b/app/code/core/Mage/Customer/Model/Entity/Address/Attribute/Collection.php @@ -0,0 +1,69 @@ + + */ +class Mage_Customer_Model_Entity_Address_Attribute_Collection extends Mage_Eav_Model_Mysql4_Entity_Attribute_Collection +{ + protected function _initSelect() + { + $this->getSelect()->from(array('main_table' => $this->getResource()->getMainTable())) + ->where('main_table.entity_type_id=?', Mage::getModel('eav/entity')->setType('customer_address')->getTypeId()) + ->join( + array('additional_table' => $this->getTable('customer/eav_attribute')), + 'additional_table.attribute_id=main_table.attribute_id' + ); + return $this; + } + + /** + * Specify attribute entity type filter + * + * @param int $typeId + * @return Mage_Customer_Model_Entity_Address_Attribute_Collection + */ + public function setEntityTypeFilter($typeId) + { + return $this; + } + + /** + * Specify filter by "is_visible" field + * + * @return Mage_Customer_Model_Entity_Address_Attribute_Collection + */ + public function addVisibleFilter() + { + $this->getSelect()->where('additional_table.is_visible=?', 1); + return $this; + } +} diff --git a/app/code/core/Mage/Customer/Model/Entity/Attribute.php b/app/code/core/Mage/Customer/Model/Entity/Attribute.php new file mode 100644 index 0000000000..fa7eb6429e --- /dev/null +++ b/app/code/core/Mage/Customer/Model/Entity/Attribute.php @@ -0,0 +1,53 @@ + + */ +class Mage_Customer_Model_Entity_Attribute extends Mage_Eav_Model_Mysql4_Entity_Attribute +{ + /** + * Perform actions before object save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Customer_Model_Entity_Attribute + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + $inputFilter = $object->getInputFilter(); + if (is_array($inputFilter)) { + $object->setInputFilter(implode(',', $inputFilter)); + } + return parent::_beforeSave($object); + } + + +} diff --git a/app/code/core/Mage/Customer/Model/Entity/Attribute/Collection.php b/app/code/core/Mage/Customer/Model/Entity/Attribute/Collection.php new file mode 100644 index 0000000000..ba17cbb2f9 --- /dev/null +++ b/app/code/core/Mage/Customer/Model/Entity/Attribute/Collection.php @@ -0,0 +1,69 @@ + + */ +class Mage_Customer_Model_Entity_Attribute_Collection extends Mage_Eav_Model_Mysql4_Entity_Attribute_Collection +{ + protected function _initSelect() + { + $this->getSelect()->from(array('main_table' => $this->getResource()->getMainTable())) + ->where('main_table.entity_type_id=?', Mage::getModel('eav/entity')->setType('customer')->getTypeId()) + ->join( + array('additional_table' => $this->getTable('customer/eav_attribute')), + 'additional_table.attribute_id=main_table.attribute_id' + ); + return $this; + } + + /** + * Specify attribute entity type filter + * + * @param int $typeId + * @return Mage_Customer_Model_Entity_Attribute_Collection + */ + public function setEntityTypeFilter($typeId) + { + return $this; + } + + /** + * Specify filter by "is_visible" field + * + * @return Mage_Customer_Model_Entity_Attribute_Collection + */ + public function addVisibleFilter() + { + $this->getSelect()->where('additional_table.is_visible=?', 1); + return $this; + } +} diff --git a/app/code/core/Mage/Customer/Model/Entity/Customer/Collection.php b/app/code/core/Mage/Customer/Model/Entity/Customer/Collection.php index 1335a9f962..9579e8a19e 100644 --- a/app/code/core/Mage/Customer/Model/Entity/Customer/Collection.php +++ b/app/code/core/Mage/Customer/Model/Entity/Customer/Collection.php @@ -91,21 +91,15 @@ public function getSelectCountSql() } /** - * Retrive all ids for collection + * Reset left join * - * @return array + * @return Mage_Eav_Model_Entity_Collection_Abstract */ - public function getAllIds($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->from(null, 'e.'.$this->getEntity()->getIdFieldName()); - $idsSelect->limit($limit, $offset); + $idsSelect = parent::_getAllIdsSelect($limit, $offset); $idsSelect->resetJoinLeft(); - - return $this->getConnection()->fetchCol($idsSelect, $this->_bindParams); + return $idsSelect; } -} \ No newline at end of file + +} diff --git a/app/code/core/Mage/Customer/Model/Entity/Setup.php b/app/code/core/Mage/Customer/Model/Entity/Setup.php index cef4830b6a..3d796143e7 100644 --- a/app/code/core/Mage/Customer/Model/Entity/Setup.php +++ b/app/code/core/Mage/Customer/Model/Entity/Setup.php @@ -34,6 +34,26 @@ class Mage_Customer_Model_Entity_Setup extends Mage_Eav_Model_Entity_Setup { + /** + * Prepare customer attribute values to save + * + * @param array $attr + * @return array + */ + protected function _prepareValues($attr) + { + $data = parent::_prepareValues($attr); + $data = array_merge($data, array( + 'is_visible' => $this->_getValue($attr, 'visible', 1), + 'is_visible_on_front' => $this->_getValue($attr, 'visible_on_front', 0), + 'input_filter' => $this->_getValue($attr, 'input_filter', ''), + 'lines_to_divide_multiline' => $this->_getValue($attr, 'lines_to_divide', 0), + 'min_text_length' => $this->_getValue($attr, 'min_text_length', 0), + 'max_text_length' => $this->_getValue($attr, 'max_text_length', 0) + )); + return $data; + } + public function getDefaultEntities() { return array( @@ -42,6 +62,8 @@ public function getDefaultEntities() 'table' => 'customer/entity', 'increment_model' => 'eav/entity_increment_numeric', 'increment_per_store' => false, + 'additional_attribute_table' => 'customer/eav_attribute', + 'entity_attribute_collection' => 'customer/eav_attribute', 'attributes' => array( // 'entity_id' => array('type'=>'static'), // 'entity_type_id' => array('type'=>'static'), @@ -105,7 +127,7 @@ public function getDefaultEntities() 'group_id' => array( 'type' => 'static', 'input' => 'select', - 'label' => 'Customer Group', + 'label' => 'Group', 'source' => 'customer/customer_attribute_source_group', 'sort_order' => 70, ), @@ -138,7 +160,6 @@ public function getDefaultEntities() 'label' => 'Tax/VAT number', 'visible' => true, 'required' => false, - 'position' => 1, ), 'confirmation' => array( 'label' => 'Is confirmed', @@ -151,6 +172,8 @@ public function getDefaultEntities() 'customer_address'=>array( 'entity_model' =>'customer/customer_address', 'table' => 'customer/address_entity', + 'additional_attribute_table' => 'customer/eav_attribute', + 'entity_attribute_collection' => 'customer/eav_attribute', 'attributes' => array( // 'entity_id' => array('type'=>'static'), // 'entity_type_id' => array('type'=>'static'), @@ -220,6 +243,7 @@ public function getDefaultEntities() 'source' => 'customer_entity/address_attribute_source_region', 'required' => 'false', 'sort_order' => 80, + 'label' => 'State/Province' ), 'postcode' => array( 'label' => 'Zip/Postal Code', diff --git a/app/code/core/Mage/Customer/Model/Group.php b/app/code/core/Mage/Customer/Model/Group.php index fb67b289c4..a8ecb7dd08 100644 --- a/app/code/core/Mage/Customer/Model/Group.php +++ b/app/code/core/Mage/Customer/Model/Group.php @@ -1,102 +1,119 @@ - - */ -class Mage_Customer_Model_Group extends Mage_Core_Model_Abstract -{ - const XML_PATH_DEFAULT_ID = 'customer/create_account/default_group'; - const NOT_LOGGED_IN_ID = 0; - const CUST_GROUP_ALL = 32000; - - /** - * Prefix of model events names - * - * @var string - */ - protected $_eventPrefix = 'customer_group'; - - /** - * Parameter name in event - * - * In observe method you can use $observer->getEvent()->getObject() in this case - * - * @var string - */ - protected $_eventObject = 'object'; - - protected static $_taxClassIds = array(); - - protected function _construct() - { - $this->_init('customer/group'); - } - - /** - * Alias for setCustomerGroupCode - * - * @param string $value - */ - public function setCode($value) - { - return $this->setCustomerGroupCode($value); - } - - /** - * Alias for getCustomerGroupCode - * - * @return string - */ - public function getCode() - { - return $this->getCustomerGroupCode(); - } - - public function getTaxClassId($groupId=null) - { - if (!is_null($groupId)) { - if (empty(self::$_taxClassIds[$groupId])) { - $this->load($groupId); - self::$_taxClassIds[$groupId] = $this->getData('tax_class_id'); - } - $this->setData('tax_class_id', self::$_taxClassIds[$groupId]); - } - return $this->getData('tax_class_id'); - } - - - public function usesAsDefault() - { - $data = Mage::getConfig()->getStoresConfigByPath(self::XML_PATH_DEFAULT_ID); - if (in_array($this->getId(), $data)) { - return true; - } - return false; - } -} \ No newline at end of file + + */ +class Mage_Customer_Model_Group extends Mage_Core_Model_Abstract +{ + const XML_PATH_DEFAULT_ID = 'customer/create_account/default_group'; + + const NOT_LOGGED_IN_ID = 0; + const CUST_GROUP_ALL = 32000; + + const ENTITY = 'customer_group'; + + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'customer_group'; + + /** + * Parameter name in event + * + * In observe method you can use $observer->getEvent()->getObject() in this case + * + * @var string + */ + protected $_eventObject = 'object'; + + protected static $_taxClassIds = array(); + + protected function _construct() + { + $this->_init('customer/group'); + } + + /** + * Alias for setCustomerGroupCode + * + * @param string $value + */ + public function setCode($value) + { + return $this->setCustomerGroupCode($value); + } + + /** + * Alias for getCustomerGroupCode + * + * @return string + */ + public function getCode() + { + return $this->getCustomerGroupCode(); + } + + public function getTaxClassId($groupId=null) + { + if (!is_null($groupId)) { + if (empty(self::$_taxClassIds[$groupId])) { + $this->load($groupId); + self::$_taxClassIds[$groupId] = $this->getData('tax_class_id'); + } + $this->setData('tax_class_id', self::$_taxClassIds[$groupId]); + } + return $this->getData('tax_class_id'); + } + + + public function usesAsDefault() + { + $data = Mage::getConfig()->getStoresConfigByPath(self::XML_PATH_DEFAULT_ID); + if (in_array($this->getId(), $data)) { + return true; + } + return false; + } + + /** + * Processing data save after transaction commit + * + * @return Mage_Customer_Model_Group + */ + protected function _afterSaveCommit() + { + parent::_afterSaveCommit(); + Mage::getSingleton('index/indexer')->processEntityAction( + $this, self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE + ); + return $this; + } +} diff --git a/app/code/core/Mage/Customer/controllers/AccountController.php b/app/code/core/Mage/Customer/controllers/AccountController.php index dc98ca5f8a..f942838099 100644 --- a/app/code/core/Mage/Customer/controllers/AccountController.php +++ b/app/code/core/Mage/Customer/controllers/AccountController.php @@ -188,6 +188,8 @@ protected function _loginPostRedirect() } else { $session->setBeforeAuthUrl(Mage::helper('customer')->getLoginUrl()); } + } else if ($session->getBeforeAuthUrl() == Mage::helper('customer')->getLogoutUrl()) { + $session->setBeforeAuthUrl(Mage::helper('customer')->getDashboardUrl()); } $this->_redirectUrl($session->getBeforeAuthUrl(true)); @@ -246,6 +248,9 @@ public function createPostAction() foreach (Mage::getConfig()->getFieldset('customer_account') as $code=>$node) { if ($node->is('create') && ($value = $this->getRequest()->getParam($code)) !== null) { + if ($code == 'email') { + $value = trim($value); + } $customer->setData($code, $value); } } @@ -318,7 +323,10 @@ public function createPostAction() ->addException($e, $this->__('Can\'t save customer')); } } - + /** + * Protect XSS injection in user input + */ + $this->_getSession()->setEscapeMessages(true); $this->_redirectError(Mage::getUrl('*/*/create', array('_secure'=>true))); } diff --git a/app/code/core/Mage/Customer/etc/adminhtml.xml b/app/code/core/Mage/Customer/etc/adminhtml.xml new file mode 100644 index 0000000000..0fd6a9c9d0 --- /dev/null +++ b/app/code/core/Mage/Customer/etc/adminhtml.xml @@ -0,0 +1,91 @@ + + + + + + Customers + 40 + + + + Manage Customers + adminhtml/customer/ + admin/customerlist + + + Customer Groups + adminhtml/customer_group/ + admin/customer/group + + + Online Customers + adminhtml/customer_online/ + admin/customer/online + + + + + + + + + + Customers + 40 + + + Customer Groups + 0 + + + Manage Customers + 10 + + + Online Customers + 20 + + + + + + + + + Customers Section + 50 + + + + + + + + + + diff --git a/app/code/core/Mage/Customer/etc/config.xml b/app/code/core/Mage/Customer/etc/config.xml index f05f5af695..36cd15f621 100644 --- a/app/code/core/Mage/Customer/etc/config.xml +++ b/app/code/core/Mage/Customer/etc/config.xml @@ -28,7 +28,7 @@ - 0.8.11 + 1.4.0.0.2 @@ -91,6 +91,7 @@ 1 11 11 + 11 @@ -180,6 +181,9 @@ T: {{var telephone}} customer_group + + customer_eav_attribute + @@ -189,20 +193,7 @@ T: {{var telephone}} Mage_Customer Mage_Customer_Model_Entity_Setup - - core_setup - - - - core_write - - - - - core_read - - @@ -240,68 +231,6 @@ T: {{var telephone}} - - - Customers - 40 - - - - Manage Customers - adminhtml/customer/ - admin/customerlist - - - Customer Groups - adminhtml/customer_group/ - admin/customer/group - - - Online Customers - adminhtml/customer_online/ - admin/customer/online - - - - - - - - - - Customers - 40 - - - Customer Groups - 0 - - - Manage Customers - 10 - - - Online Customers - 20 - - - - - - - - - Customers Section - 50 - - - - - - - - - @@ -311,7 +240,6 @@ T: {{var telephone}} - singleton customer/observer beforeLoadLayout @@ -377,6 +305,7 @@ T: {{var telephone}} + 1 diff --git a/app/code/core/Mage/Customer/etc/system.xml b/app/code/core/Mage/Customer/etc/system.xml index 9b2edd1a50..bd278e909d 100644 --- a/app/code/core/Mage/Customer/etc/system.xml +++ b/app/code/core/Mage/Customer/etc/system.xml @@ -260,6 +260,15 @@ 1 0 + + Show gender + select + adminhtml/system_config_source_nooptreq + 90 + 1 + 1 + 0 + diff --git a/app/code/core/Mage/Customer/sql/customer_setup/mysql4-install-1.4.0.0.0.php b/app/code/core/Mage/Customer/sql/customer_setup/mysql4-install-1.4.0.0.0.php new file mode 100644 index 0000000000..06e482f0a2 --- /dev/null +++ b/app/code/core/Mage/Customer/sql/customer_setup/mysql4-install-1.4.0.0.0.php @@ -0,0 +1,640 @@ + + */ +/* @var $installer Mage_Customer_Model_Entity_Setup */ +$installer = $this; + +$installer->startSetup(); +$installer->run(" +-- DROP TABLE IF EXISTS `{$installer->getTable('customer_address_entity')}`; +CREATE TABLE `{$installer->getTable('customer_address_entity')}` ( + `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 default NULL, + `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`), + KEY `FK_CUSTOMER_ADDRESS_CUSTOMER_ID` (`parent_id`), + CONSTRAINT `FK_CUSTOMER_ADDRESS_CUSTOMER_ID` FOREIGN KEY (`parent_id`) REFERENCES `{$installer->getTable('customer_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='Customer Address Entities'; + +-- DROP TABLE IF EXISTS `{$installer->getTable('customer_address_entity_datetime')}`; +CREATE TABLE `{$installer->getTable('customer_address_entity_datetime')}` ( + `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', + `entity_id` int(10) unsigned NOT NULL default '0', + `value` datetime NOT NULL default '0000-00-00 00:00:00', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`), + KEY `FK_CUSTOMER_ADDRESS_DATETIME_ENTITY_TYPE` (`entity_type_id`), + KEY `FK_CUSTOMER_ADDRESS_DATETIME_ATTRIBUTE` (`attribute_id`), + KEY `FK_CUSTOMER_ADDRESS_DATETIME_ENTITY` (`entity_id`), + KEY `IDX_VALUE` (`entity_id`,`attribute_id`,`value`), + CONSTRAINT `FK_CUSTOMER_ADDRESS_DATETIME_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav_attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_ADDRESS_DATETIME_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES `{$installer->getTable('customer_address_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_ADDRESS_DATETIME_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$installer->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('customer_address_entity_decimal')}`; +CREATE TABLE `{$installer->getTable('customer_address_entity_decimal')}` ( + `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', + `entity_id` int(10) unsigned NOT NULL default '0', + `value` decimal(12,4) NOT NULL default '0.0000', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`), + KEY `FK_CUSTOMER_ADDRESS_DECIMAL_ENTITY_TYPE` (`entity_type_id`), + KEY `FK_CUSTOMER_ADDRESS_DECIMAL_ATTRIBUTE` (`attribute_id`), + KEY `FK_CUSTOMER_ADDRESS_DECIMAL_ENTITY` (`entity_id`), + KEY `IDX_VALUE` (`entity_id`,`attribute_id`,`value`), + CONSTRAINT `FK_CUSTOMER_ADDRESS_DECIMAL_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav_attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_ADDRESS_DECIMAL_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES `{$installer->getTable('customer_address_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_ADDRESS_DECIMAL_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$installer->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('customer_address_entity_int')}`; +CREATE TABLE `{$installer->getTable('customer_address_entity_int')}` ( + `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', + `entity_id` int(10) unsigned NOT NULL default '0', + `value` int(11) NOT NULL default '0', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`), + KEY `FK_CUSTOMER_ADDRESS_INT_ENTITY_TYPE` (`entity_type_id`), + KEY `FK_CUSTOMER_ADDRESS_INT_ATTRIBUTE` (`attribute_id`), + KEY `FK_CUSTOMER_ADDRESS_INT_ENTITY` (`entity_id`), + KEY `IDX_VALUE` (`entity_id`,`attribute_id`,`value`), + CONSTRAINT `FK_CUSTOMER_ADDRESS_INT_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav_attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_ADDRESS_INT_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES `{$installer->getTable('customer_address_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_ADDRESS_INT_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$installer->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('customer_address_entity_text')}`; +CREATE TABLE `{$installer->getTable('customer_address_entity_text')}` ( + `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', + `entity_id` int(10) unsigned NOT NULL default '0', + `value` text NOT NULL, + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`), + KEY `FK_CUSTOMER_ADDRESS_TEXT_ENTITY_TYPE` (`entity_type_id`), + KEY `FK_CUSTOMER_ADDRESS_TEXT_ATTRIBUTE` (`attribute_id`), + KEY `FK_CUSTOMER_ADDRESS_TEXT_ENTITY` (`entity_id`), + CONSTRAINT `FK_CUSTOMER_ADDRESS_TEXT_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav_attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_ADDRESS_TEXT_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES `{$installer->getTable('customer_address_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_ADDRESS_TEXT_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$installer->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('customer_address_entity_varchar')}`; +CREATE TABLE `{$installer->getTable('customer_address_entity_varchar')}` ( + `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', + `entity_id` int(10) unsigned NOT NULL default '0', + `value` varchar(255) NOT NULL default '', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`), + KEY `FK_CUSTOMER_ADDRESS_VARCHAR_ENTITY_TYPE` (`entity_type_id`), + KEY `FK_CUSTOMER_ADDRESS_VARCHAR_ATTRIBUTE` (`attribute_id`), + KEY `FK_CUSTOMER_ADDRESS_VARCHAR_ENTITY` (`entity_id`), + KEY `IDX_VALUE` (`entity_id`,`attribute_id`,`value`), + CONSTRAINT `FK_CUSTOMER_ADDRESS_VARCHAR_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav_attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_ADDRESS_VARCHAR_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES `{$installer->getTable('customer_address_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_ADDRESS_VARCHAR_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$installer->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('customer_entity')}`; +CREATE TABLE `{$installer->getTable('customer_entity')}` ( + `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', + `website_id` smallint(5) unsigned default NULL, + `email` varchar(255) NOT NULL default '', + `group_id` smallint(3) unsigned NOT NULL default '0', + `increment_id` varchar(50) NOT NULL default '', + `store_id` smallint(5) unsigned 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`), + KEY `FK_CUSTOMER_ENTITY_STORE` (`store_id`), + KEY `IDX_ENTITY_TYPE` (`entity_type_id`), + KEY `IDX_AUTH` (`email`,`website_id`), + KEY `FK_CUSTOMER_WEBSITE` (`website_id`), + CONSTRAINT `FK_CUSTOMER_ENTITY_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core_store')}` (`store_id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_WEBSITE` FOREIGN KEY (`website_id`) REFERENCES `{$installer->getTable('core_website')}` (`website_id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='Customer Entityies'; + +-- DROP TABLE IF EXISTS `{$installer->getTable('customer_entity_datetime')}`; +CREATE TABLE `{$installer->getTable('customer_entity_datetime')}` ( + `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', + `entity_id` int(10) unsigned NOT NULL default '0', + `value` datetime NOT NULL default '0000-00-00 00:00:00', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`), + KEY `FK_CUSTOMER_DATETIME_ENTITY_TYPE` (`entity_type_id`), + KEY `FK_CUSTOMER_DATETIME_ATTRIBUTE` (`attribute_id`), + KEY `FK_CUSTOMER_DATETIME_ENTITY` (`entity_id`), + KEY `IDX_VALUE` (`entity_id`,`attribute_id`,`value`), + CONSTRAINT `FK_CUSTOMER_DATETIME_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav_attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_DATETIME_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES `{$installer->getTable('customer_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_DATETIME_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$installer->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('customer_entity_decimal')}`; +CREATE TABLE `{$installer->getTable('customer_entity_decimal')}` ( + `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', + `entity_id` int(10) unsigned NOT NULL default '0', + `value` decimal(12,4) NOT NULL default '0.0000', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`), + KEY `FK_CUSTOMER_DECIMAL_ENTITY_TYPE` (`entity_type_id`), + KEY `FK_CUSTOMER_DECIMAL_ATTRIBUTE` (`attribute_id`), + KEY `FK_CUSTOMER_DECIMAL_ENTITY` (`entity_id`), + KEY `IDX_VALUE` (`entity_id`,`attribute_id`,`value`), + CONSTRAINT `FK_CUSTOMER_DECIMAL_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav_attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_DECIMAL_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES `{$installer->getTable('customer_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_DECIMAL_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$installer->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('customer_entity_int')}`; +CREATE TABLE `{$installer->getTable('customer_entity_int')}` ( + `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', + `entity_id` int(10) unsigned NOT NULL default '0', + `value` int(11) NOT NULL default '0', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`), + KEY `FK_CUSTOMER_INT_ENTITY_TYPE` (`entity_type_id`), + KEY `FK_CUSTOMER_INT_ATTRIBUTE` (`attribute_id`), + KEY `FK_CUSTOMER_INT_ENTITY` (`entity_id`), + KEY `IDX_VALUE` (`entity_id`,`attribute_id`,`value`), + CONSTRAINT `FK_CUSTOMER_INT_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav_attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_INT_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES `{$installer->getTable('customer_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_INT_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$installer->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('customer_entity_text')}`; +CREATE TABLE `{$installer->getTable('customer_entity_text')}` ( + `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', + `entity_id` int(10) unsigned NOT NULL default '0', + `value` text NOT NULL, + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`), + KEY `FK_CUSTOMER_TEXT_ENTITY_TYPE` (`entity_type_id`), + KEY `FK_CUSTOMER_TEXT_ATTRIBUTE` (`attribute_id`), + KEY `FK_CUSTOMER_TEXT_ENTITY` (`entity_id`), + CONSTRAINT `FK_CUSTOMER_TEXT_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav_attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_TEXT_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES `{$installer->getTable('customer_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_TEXT_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$installer->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('customer_entity_varchar')}`; +CREATE TABLE `{$installer->getTable('customer_entity_varchar')}` ( + `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', + `entity_id` int(10) unsigned NOT NULL default '0', + `value` varchar(255) NOT NULL default '', + PRIMARY KEY (`value_id`), + UNIQUE KEY `IDX_ATTRIBUTE_VALUE` (`entity_id`,`attribute_id`), + KEY `FK_CUSTOMER_VARCHAR_ENTITY_TYPE` (`entity_type_id`), + KEY `FK_CUSTOMER_VARCHAR_ATTRIBUTE` (`attribute_id`), + KEY `FK_CUSTOMER_VARCHAR_ENTITY` (`entity_id`), + KEY `IDX_VALUE` (`entity_id`,`attribute_id`,`value`), + CONSTRAINT `FK_CUSTOMER_VARCHAR_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav_attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_VARCHAR_ENTITY` FOREIGN KEY (`entity_id`) REFERENCES `{$installer->getTable('customer_entity')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_CUSTOMER_VARCHAR_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$installer->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `{$installer->getTable('customer_group')}`; +CREATE TABLE `{$installer->getTable('customer_group')}` ( + `customer_group_id` smallint(3) unsigned NOT NULL auto_increment, + `customer_group_code` varchar(32) NOT NULL default '', + `tax_class_id` int(10) unsigned NOT NULL default '0', + PRIMARY KEY (`customer_group_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Customer groups'; + +INSERT INTO `{$installer->getTable('customer_group')}` VALUES(0, 'NOT LOGGED IN', 3), (1, 'General', 3), (2, 'Wholesale', 3), (3, 'Retailer', 3); + +-- DROP TABLE IF EXISTS `{$installer->getTable('customer/eav_attribute')}`; +CREATE TABLE `{$installer->getTable('customer/eav_attribute')}` ( + `attribute_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, + `is_visible` tinyint(1) unsigned NOT NULL DEFAULT '1', + `is_visible_on_front` tinyint(1) unsigned NOT NULL DEFAULT '0', + `input_filter` varchar(255) NOT NULL, + `lines_to_divide_multiline` smallint(5) unsigned NOT NULL DEFAULT '0', + `min_text_length` int(11) unsigned NOT NULL DEFAULT '0', + `max_text_length` int(11) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`attribute_id`), + CONSTRAINT `FK_CUSTOMER_EAV_ATTRIBUTE_ID` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav/attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +"); +$installer->endSetup(); +$installer->installEntities(); + + +$setup = $installer->getConnection(); + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/prefix_show') + ->where('value!=?', '0'); +$showPrefix = (bool)Mage::helper('customer/address')->getConfig('prefix_show') + || $setup->fetchOne($select) > 0; + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/middlename_show') + ->where('value!=?', '0'); +$showMiddlename = (bool)Mage::helper('customer/address')->getConfig('middlename_show') + || $setup->fetchOne($select) > 0; + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/suffix_show') + ->where('value!=?', '0'); +$showSuffix = (bool)Mage::helper('customer/address')->getConfig('suffix_show') + || $setup->fetchOne($select) > 0; + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/dob_show') + ->where('value!=?', '0'); +$showDob = (bool)Mage::helper('customer/address')->getConfig('dob_show') + || $setup->fetchOne($select) > 0; + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/taxvat_show') + ->where('value!=?', '0'); +$showTaxVat = (bool)Mage::helper('customer/address')->getConfig('taxvat_show') + || $setup->fetchOne($select) > 0; + +/** + ***************************************************************************** + * customer/account/create/ + ***************************************************************************** + */ + +$setup->insert($installer->getTable('eav/form_type'), array( + 'code' => 'customer_account_create', + 'label' => 'customer_account_create', + 'is_system' => 1, + 'theme' => '', + 'store_id' => 0 +)); +$formTypeId = $setup->lastInsertId(); +$entityTypeId = $installer->getEntityTypeId('customer'); + +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $entityTypeId +)); + +$setup->insert($installer->getTable('eav/form_fieldset'), array( + 'type_id' => $formTypeId, + 'code' => 'general', + 'sort_order' => 1 +)); +$fieldsetId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_fieldset_label'), array( + 'fieldset_id' => $fieldsetId, + 'store_id' => 0, + 'label' => 'Personal Information' +)); + +$elementSort = 0; +if ($showPrefix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'prefix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'firstname'), + 'sort_order' => $elementSort++ +)); +if ($showMiddlename) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'middlename'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'lastname'), + 'sort_order' => $elementSort++ +)); +if ($showSuffix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'suffix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'email'), + 'sort_order' => $elementSort++ +)); +if ($showDob) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'dob'), + 'sort_order' => $elementSort++ + )); +} +if ($showTaxVat) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'taxvat'), + 'sort_order' => $elementSort++ + )); +} + +/** + ***************************************************************************** + * customer/account/edit/ + ***************************************************************************** + */ + +$setup->insert($installer->getTable('eav/form_type'), array( + 'code' => 'customer_account_edit', + 'label' => 'customer_account_edit', + 'is_system' => 1, + 'theme' => '', + 'store_id' => 0 +)); +$formTypeId = $setup->lastInsertId(); +$entityTypeId = $installer->getEntityTypeId('customer'); + +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $entityTypeId +)); + +$setup->insert($installer->getTable('eav/form_fieldset'), array( + 'type_id' => $formTypeId, + 'code' => 'general', + 'sort_order' => 1 +)); +$fieldsetId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_fieldset_label'), array( + 'fieldset_id' => $fieldsetId, + 'store_id' => 0, + 'label' => 'Account Information' +)); + +$elementSort = 0; +if ($showPrefix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'prefix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'firstname'), + 'sort_order' => $elementSort++ +)); +if ($showMiddlename) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'middlename'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'lastname'), + 'sort_order' => $elementSort++ +)); +if ($showSuffix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'suffix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'email'), + 'sort_order' => $elementSort++ +)); +if ($showDob) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'dob'), + 'sort_order' => $elementSort++ + )); +} +if ($showTaxVat) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'taxvat'), + 'sort_order' => $elementSort++ + )); +} + +/** + ***************************************************************************** + * customer/address/edit + ***************************************************************************** + */ + +$setup->insert($installer->getTable('eav/form_type'), array( + 'code' => 'customer_address_edit', + 'label' => 'customer_address_edit', + 'is_system' => 1, + 'theme' => '', + 'store_id' => 0 +)); +$formTypeId = $setup->lastInsertId(); +$entityTypeId = $installer->getEntityTypeId('customer_address'); + +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $entityTypeId +)); + +$setup->insert($installer->getTable('eav/form_fieldset'), array( + 'type_id' => $formTypeId, + 'code' => 'contact', + 'sort_order' => 1 +)); +$fieldsetId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_fieldset_label'), array( + 'fieldset_id' => $fieldsetId, + 'store_id' => 0, + 'label' => 'Contact Information' +)); + +$elementSort = 0; +if ($showPrefix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'prefix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'firstname'), + 'sort_order' => $elementSort++ +)); +if ($showMiddlename) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'middlename'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'lastname'), + 'sort_order' => $elementSort++ +)); +if ($showSuffix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'suffix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'company'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'telephone'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'fax'), + 'sort_order' => $elementSort++ +)); + +$setup->insert($installer->getTable('eav/form_fieldset'), array( + 'type_id' => $formTypeId, + 'code' => 'address', + 'sort_order' => 2 +)); +$fieldsetId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_fieldset_label'), array( + 'fieldset_id' => $fieldsetId, + 'store_id' => 0, + 'label' => 'Address' +)); + +$elementSort = 0; +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'street'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'city'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'region'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'postcode'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'country_id'), + 'sort_order' => $elementSort++ +)); \ No newline at end of file diff --git a/app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-0.8.11-0.8.12.php b/app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-0.8.11-0.8.12.php new file mode 100644 index 0000000000..ac5b35c2a7 --- /dev/null +++ b/app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-0.8.11-0.8.12.php @@ -0,0 +1,392 @@ +startSetup(); +$setup = $installer->getConnection(); + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/prefix_show') + ->where('value!=?', '0'); +$showPrefix = (bool)Mage::helper('customer/address')->getConfig('prefix_show') + || $setup->fetchOne($select) > 0; + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/middlename_show') + ->where('value!=?', '0'); +$showMiddlename = (bool)Mage::helper('customer/address')->getConfig('middlename_show') + || $setup->fetchOne($select) > 0; + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/suffix_show') + ->where('value!=?', '0'); +$showSuffix = (bool)Mage::helper('customer/address')->getConfig('suffix_show') + || $setup->fetchOne($select) > 0; + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/dob_show') + ->where('value!=?', '0'); +$showDob = (bool)Mage::helper('customer/address')->getConfig('dob_show') + || $setup->fetchOne($select) > 0; + +$select = $setup->select() + ->from($installer->getTable('core/config_data'), 'COUNT(*)') + ->where('path=?', 'customer/address/taxvat_show') + ->where('value!=?', '0'); +$showTaxVat = (bool)Mage::helper('customer/address')->getConfig('taxvat_show') + || $setup->fetchOne($select) > 0; + +/** + ***************************************************************************** + * customer/account/create/ + ***************************************************************************** + */ + +$setup->insert($installer->getTable('eav/form_type'), array( + 'code' => 'customer_account_create', + 'label' => 'customer_account_create', + 'is_system' => 1, + 'theme' => '', + 'store_id' => 0 +)); +$formTypeId = $setup->lastInsertId(); +$entityTypeId = $installer->getEntityTypeId('customer'); + +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $entityTypeId +)); + +$setup->insert($installer->getTable('eav/form_fieldset'), array( + 'type_id' => $formTypeId, + 'code' => 'general', + 'sort_order' => 1 +)); +$fieldsetId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_fieldset_label'), array( + 'fieldset_id' => $fieldsetId, + 'store_id' => 0, + 'label' => 'Personal Information' +)); + +$elementSort = 0; +if ($showPrefix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'prefix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'firstname'), + 'sort_order' => $elementSort++ +)); +if ($showMiddlename) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'middlename'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'lastname'), + 'sort_order' => $elementSort++ +)); +if ($showSuffix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'suffix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'email'), + 'sort_order' => $elementSort++ +)); +if ($showDob) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'dob'), + 'sort_order' => $elementSort++ + )); +} +if ($showTaxVat) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'taxvat'), + 'sort_order' => $elementSort++ + )); +} + +/** + ***************************************************************************** + * customer/account/edit/ + ***************************************************************************** + */ + +$setup->insert($installer->getTable('eav/form_type'), array( + 'code' => 'customer_account_edit', + 'label' => 'customer_account_edit', + 'is_system' => 1, + 'theme' => '', + 'store_id' => 0 +)); +$formTypeId = $setup->lastInsertId(); +$entityTypeId = $installer->getEntityTypeId('customer'); + +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $entityTypeId +)); + +$setup->insert($installer->getTable('eav/form_fieldset'), array( + 'type_id' => $formTypeId, + 'code' => 'general', + 'sort_order' => 1 +)); +$fieldsetId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_fieldset_label'), array( + 'fieldset_id' => $fieldsetId, + 'store_id' => 0, + 'label' => 'Account Information' +)); + +$elementSort = 0; +if ($showPrefix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'prefix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'firstname'), + 'sort_order' => $elementSort++ +)); +if ($showMiddlename) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'middlename'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'lastname'), + 'sort_order' => $elementSort++ +)); +if ($showSuffix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'suffix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'email'), + 'sort_order' => $elementSort++ +)); +if ($showDob) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'dob'), + 'sort_order' => $elementSort++ + )); +} +if ($showTaxVat) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'taxvat'), + 'sort_order' => $elementSort++ + )); +} + +/** + ***************************************************************************** + * customer/address/edit + ***************************************************************************** + */ + +$setup->insert($installer->getTable('eav/form_type'), array( + 'code' => 'customer_address_edit', + 'label' => 'customer_address_edit', + 'is_system' => 1, + 'theme' => '', + 'store_id' => 0 +)); +$formTypeId = $setup->lastInsertId(); +$entityTypeId = $installer->getEntityTypeId('customer_address'); + +$setup->insert($installer->getTable('eav/form_type_entity'), array( + 'type_id' => $formTypeId, + 'entity_type_id' => $entityTypeId +)); + +$setup->insert($installer->getTable('eav/form_fieldset'), array( + 'type_id' => $formTypeId, + 'code' => 'contact', + 'sort_order' => 1 +)); +$fieldsetId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_fieldset_label'), array( + 'fieldset_id' => $fieldsetId, + 'store_id' => 0, + 'label' => 'Contact Information' +)); + +$elementSort = 0; +if ($showPrefix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'prefix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'firstname'), + 'sort_order' => $elementSort++ +)); +if ($showMiddlename) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'middlename'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'lastname'), + 'sort_order' => $elementSort++ +)); +if ($showSuffix) { + $setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'suffix'), + 'sort_order' => $elementSort++ + )); +} +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'company'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'telephone'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'fax'), + 'sort_order' => $elementSort++ +)); + +$setup->insert($installer->getTable('eav/form_fieldset'), array( + 'type_id' => $formTypeId, + 'code' => 'address', + 'sort_order' => 2 +)); +$fieldsetId = $setup->lastInsertId(); + +$setup->insert($installer->getTable('eav/form_fieldset_label'), array( + 'fieldset_id' => $fieldsetId, + 'store_id' => 0, + 'label' => 'Address' +)); + +$elementSort = 0; +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'street'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'city'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'region'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'postcode'), + 'sort_order' => $elementSort++ +)); +$setup->insert($installer->getTable('eav/form_element'), array( + 'type_id' => $formTypeId, + 'fieldset_id' => $fieldsetId, + 'attribute_id' => $installer->getAttributeId($entityTypeId, 'country_id'), + 'sort_order' => $elementSort++ +)); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-0.8.12-1.4.0.0.0.php b/app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-0.8.12-1.4.0.0.0.php new file mode 100644 index 0000000000..b324bc9db4 --- /dev/null +++ b/app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-0.8.12-1.4.0.0.0.php @@ -0,0 +1,78 @@ +startSetup(); + +$installer->updateEntityType('customer', 'additional_attribute_table', 'customer/eav_attribute'); +$installer->updateEntityType('customer', 'entity_attribute_collection', 'customer/attribute_collection'); +$installer->updateEntityType('customer_address', 'additional_attribute_table', 'customer/eav_attribute'); +$installer->updateEntityType('customer_address', 'entity_attribute_collection', 'customer/address_attribute_collection'); +$installer->run(" +CREATE TABLE `{$installer->getTable('customer/eav_attribute')}` ( + `attribute_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, + `is_visible` tinyint(1) unsigned NOT NULL DEFAULT '1', + `is_visible_on_front` tinyint(1) unsigned NOT NULL DEFAULT '0', + `input_filter` varchar(255) NOT NULL, + `lines_to_divide_multiline` smallint(5) unsigned NOT NULL DEFAULT '0', + `min_text_length` int(11) unsigned NOT NULL DEFAULT '0', + `max_text_length` int(11) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`attribute_id`), + CONSTRAINT `FK_CUSTOMER_EAV_ATTRIBUTE_ID` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav/attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +"); +$visibleAttributes = array('store_id', 'default_billing', 'default_shipping', 'confirmation'); +$stmt = $installer->getConnection()->select() + ->from($installer->getTable('eav/attribute'), array('attribute_id', 'attribute_code')) + ->where('entity_type_id = ?', $installer->getEntityTypeId('customer')) + ->orWhere('entity_type_id = ?', $installer->getEntityTypeId('customer_address')); +$result = $installer->getConnection()->fetchAll($stmt); +$attributes = array(); +foreach ($result as $row) { + $_visible = true; + $_visibleOnFront = false; + $_inputFilter = ''; + $_linesToDivideMultiline = 0; + $_minLength = 0; + $_maxLength = 0; + if (in_array($row['attribute_code'], $visibleAttributes)) { + $_visible = false; + } + $attributes[] = array( + 'attribute_id' => $row['attribute_id'], + 'is_visible' => $_visible, + 'is_visible_on_front' => $_visibleOnFront, + 'input_filter' => $_inputFilter, + 'lines_to_divide_multiline' => $_linesToDivideMultiline, + 'min_text_length' => $_minLength, + 'max_text_length' => $_maxLength + ); +} +$installer->getConnection()->insertMultiple($installer->getTable('customer/eav_attribute'), $attributes); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-1.4.0.0.0-1.4.0.0.1.php b/app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-1.4.0.0.0-1.4.0.0.1.php new file mode 100644 index 0000000000..9b2de6b41c --- /dev/null +++ b/app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-1.4.0.0.0-1.4.0.0.1.php @@ -0,0 +1,67 @@ +startSetup(); + +$installer->addAttribute('customer', 'gender', array( + 'label' => 'Gender', + 'visible' => true, + 'required' => false, + 'type' => 'int', + 'input' => 'select', + 'source' => 'eav/entity_attribute_source_table', +)); + + +$tableOptions = $installer->getTable('eav_attribute_option'); +$tableOptionValues = $installer->getTable('eav_attribute_option_value'); + +// add options for level of politeness +$attributeId = (int)$installer->getAttribute('customer', 'gender', 'attribute_id'); +foreach (array('Male', 'Female') as $sortOrder => $label) { + + // add option + $data = array( + 'attribute_id' => $attributeId, + 'sort_order' => $sortOrder, + ); + $installer->getConnection()->insert($tableOptions, $data); + + // add option label + $optionId = (int)$installer->getConnection()->lastInsertId($tableOptions, 'option_id'); + $data = array( + 'option_id' => $optionId, + 'store_id' => 0, + 'value' => $label, + ); + $installer->getConnection()->insert($tableOptionValues, $data); + +} + +$installer->endSetup(); diff --git a/app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-1.4.0.0.1-1.4.0.0.2.php b/app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-1.4.0.0.1-1.4.0.0.2.php new file mode 100644 index 0000000000..f8b528eac9 --- /dev/null +++ b/app/code/core/Mage/Customer/sql/customer_setup/mysql4-upgrade-1.4.0.0.1-1.4.0.0.2.php @@ -0,0 +1,35 @@ +startSetup(); + +$this->updateAttribute('customer', 'default_billing', 'frontend_label', 'Default Billing Address'); +$this->updateAttribute('customer', 'default_shipping', 'frontend_label', 'Default Shipping Address'); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Cybermut/etc/config.xml b/app/code/core/Mage/Cybermut/etc/config.xml index 1e22f107a3..af1e5b0c80 100644 --- a/app/code/core/Mage/Cybermut/etc/config.xml +++ b/app/code/core/Mage/Cybermut/etc/config.xml @@ -50,20 +50,7 @@ Mage_Cybermut Mage_Cybermut_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - Mage_Cybermut_Block diff --git a/app/code/core/Mage/Cybersource/etc/config.xml b/app/code/core/Mage/Cybersource/etc/config.xml index 44032c0041..7c78413b99 100644 --- a/app/code/core/Mage/Cybersource/etc/config.xml +++ b/app/code/core/Mage/Cybersource/etc/config.xml @@ -50,20 +50,7 @@ Mage_Cybersource Mage_Cybersource_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - Mage_Cybersource_Block 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 a37dc0a622..8f2da7a123 100644 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/Collection.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/Collection.php @@ -46,7 +46,7 @@ protected function _construct() */ public function addStoreFilter($storeIds) { - $this->getSelect()->where('main_table.store_id IN (?)', $storeIds); + $this->getSelect()->where('main_table.store_id IN (?)', array(0, $storeIds)); return $this; } } \ No newline at end of file diff --git a/app/code/core/Mage/Dataflow/etc/config.xml b/app/code/core/Mage/Dataflow/etc/config.xml index 7681748ac0..2e877f8ef4 100644 --- a/app/code/core/Mage/Dataflow/etc/config.xml +++ b/app/code/core/Mage/Dataflow/etc/config.xml @@ -69,20 +69,7 @@ Mage_Dataflow - - core_setup - - - - core_write - - - - - core_read - - \ No newline at end of file diff --git a/app/code/core/Mage/Directory/Helper/Data.php b/app/code/core/Mage/Directory/Helper/Data.php index 8c5601f75c..20f5b48d41 100644 --- a/app/code/core/Mage/Directory/Helper/Data.php +++ b/app/code/core/Mage/Directory/Helper/Data.php @@ -35,6 +35,7 @@ class Mage_Directory_Helper_Data extends Mage_Core_Helper_Abstract protected $_regionCollection; protected $_regionJson; protected $_currencyCache = array(); + protected $_optionalZipCountries = null; public function getRegionCollection() { @@ -115,24 +116,30 @@ public function currencyConvert($amount, $from, $to=null) /** * Return ISO2 country codes, which have optional Zip/Postal pre-configured * - * @param mixed $storeId Store + * @param bool $asJson * @return array */ - public function getCountriesWithOptionalZip($storeId = null) + public function getCountriesWithOptionalZip($asJson = false) { - $value = Mage::getStoreConfig('general/country/optional_zip_countries', $storeId); - return preg_split('/\,/', $value, 0, PREG_SPLIT_NO_EMPTY); + if (null === $this->_optionalZipCountries) { + $this->_optionalZipCountries = preg_split('/\,/', Mage::getStoreConfig('general/country/optional_zip_countries'), + 0, PREG_SPLIT_NO_EMPTY + ); + } + if ($asJson) { + return Mage::helper('core')->jsonEncode($this->_optionalZipCountries); + } + return $this->_optionalZipCountries; } /** - * Same as getCountriesWithOptionalZip() but result is json-encoded + * Check whether zip code is optional for specified country code * - * @see getCountriesWithOptionalZip - * @param mixed $storeId Store - * @return string + * @param string $countryCode */ - public function getCountriesWithOptionalZipJson($storeId = null) + public function isZipCodeOptional($countryCode) { - return Mage::helper('core')->jsonEncode($this->getCountriesWithOptionalZip($storeId)); + $this->getCountriesWithOptionalZip(); + return in_array($countryCode, $this->_optionalZipCountries); } } diff --git a/app/code/core/Mage/Directory/Model/Currency/Filter.php b/app/code/core/Mage/Directory/Model/Currency/Filter.php index 645f5acd24..dd0dc901ec 100644 --- a/app/code/core/Mage/Directory/Model/Currency/Filter.php +++ b/app/code/core/Mage/Directory/Model/Currency/Filter.php @@ -41,7 +41,7 @@ class Mage_Directory_Model_Currency_Filter implements Zend_Filter_Interface /** * Currency object * - * @var Mage_Core_Model_Locale_Currency + * @var Zend_Currency */ protected $_currency; diff --git a/app/code/core/Mage/Directory/etc/config.xml b/app/code/core/Mage/Directory/etc/config.xml index 0aa56f69a8..339b4416d8 100644 --- a/app/code/core/Mage/Directory/etc/config.xml +++ b/app/code/core/Mage/Directory/etc/config.xml @@ -76,14 +76,7 @@ Mage_Directory - core_setup - - core_write - - - core_read - diff --git a/app/code/core/Mage/Directory/etc/system.xml b/app/code/core/Mage/Directory/etc/system.xml index ca1c60b674..bb0b0cb5ad 100644 --- a/app/code/core/Mage/Directory/etc/system.xml +++ b/app/code/core/Mage/Directory/etc/system.xml @@ -68,15 +68,6 @@ 1 1 - - Remove space from currency sign - select - adminhtml/system_config_source_yesno - 4 - 1 - 1 - 1 - @@ -194,5 +185,22 @@ + + + + + + Postal Code is Optional for + multiselect + 3 + adminhtml/system_config_source_country + 1 + 0 + 0 + + + + + diff --git a/app/code/core/Mage/Downloadable/Helper/File.php b/app/code/core/Mage/Downloadable/Helper/File.php index 86f5b75c78..56011856c7 100644 --- a/app/code/core/Mage/Downloadable/Helper/File.php +++ b/app/code/core/Mage/Downloadable/Helper/File.php @@ -33,6 +33,17 @@ */ class Mage_Downloadable_Helper_File extends Mage_Core_Helper_Abstract { + public function __construct() + { + $nodes = Mage::getConfig()->getNode('global/mime/types'); + if ($nodes) { + $nodes = (array)$nodes; + foreach ($nodes as $key => $value) { + self::$_mimeTypes[$key] = $value; + } + } + } + /** * Checking file for moving and move it * @@ -143,10 +154,619 @@ public function getFileType($filePath) protected function _getFileTypeByExt($ext) { - $type = Mage::getConfig()->getNode('global/mime/types/x' . $ext); - if ($type) { - return $type; + $type = 'x' . $ext; + if (isset(self::$_mimeTypes[$type])) { + return self::$_mimeTypes[$type]; } return 'application/octet-stream'; } + + public function getAllFileTypes() + { + return array_values(self::getAllMineTypes()); + } + + public function getAllMineTypes() + { + return self::$_mimeTypes; + } + + protected static $_mimeTypes = + array( + 'x123' => 'application/vnd.lotus-1-2-3', + 'x3dml' => 'text/vnd.in3d.3dml', + 'x3g2' => 'video/3gpp2', + 'x3gp' => 'video/3gpp', + 'xace' => 'application/x-ace-compressed', + 'xacu' => 'application/vnd.acucobol', + 'xaep' => 'application/vnd.audiograph', + 'xai' => 'application/postscript', + 'xaif' => 'audio/x-aiff', + + 'xaifc' => 'audio/x-aiff', + 'xaiff' => 'audio/x-aiff', + 'xami' => 'application/vnd.amiga.ami', + 'xapr' => 'application/vnd.lotus-approach', + 'xasf' => 'video/x-ms-asf', + 'xaso' => 'application/vnd.accpac.simply.aso', + 'xasx' => 'video/x-ms-asf', + 'xatom' => 'application/atom+xml', + 'xatomcat' => 'application/atomcat+xml', + + 'xatomsvc' => 'application/atomsvc+xml', + 'xatx' => 'application/vnd.antix.game-component', + 'xau' => 'audio/basic', + 'xavi' => 'video/x-msvideo', + 'xbat' => 'application/x-msdownload', + 'xbcpio' => 'application/x-bcpio', + 'xbdm' => 'application/vnd.syncml.dm+wbxml', + 'xbh2' => 'application/vnd.fujitsu.oasysprs', + 'xbmi' => 'application/vnd.bmi', + + 'xbmp' => 'image/bmp', + 'xbox' => 'application/vnd.previewsystems.box', + 'xboz' => 'application/x-bzip2', + 'xbtif' => 'image/prs.btif', + 'xbz' => 'application/x-bzip', + 'xbz2' => 'application/x-bzip2', + 'xcab' => 'application/vnd.ms-cab-compressed', + 'xccxml' => 'application/ccxml+xml', + 'xcdbcmsg' => 'application/vnd.contact.cmsg', + + 'xcdkey' => 'application/vnd.mediastation.cdkey', + 'xcdx' => 'chemical/x-cdx', + 'xcdxml' => 'application/vnd.chemdraw+xml', + 'xcdy' => 'application/vnd.cinderella', + 'xcer' => 'application/pkix-cert', + 'xcgm' => 'image/cgm', + 'xchat' => 'application/x-chat', + 'xchm' => 'application/vnd.ms-htmlhelp', + 'xchrt' => 'application/vnd.kde.kchart', + + 'xcif' => 'chemical/x-cif', + 'xcii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'xcil' => 'application/vnd.ms-artgalry', + 'xcla' => 'application/vnd.claymore', + 'xclkk' => 'application/vnd.crick.clicker.keyboard', + 'xclkp' => 'application/vnd.crick.clicker.palette', + 'xclkt' => 'application/vnd.crick.clicker.template', + 'xclkw' => 'application/vnd.crick.clicker.wordbank', + 'xclkx' => 'application/vnd.crick.clicker', + + 'xclp' => 'application/x-msclip', + 'xcmc' => 'application/vnd.cosmocaller', + 'xcmdf' => 'chemical/x-cmdf', + 'xcml' => 'chemical/x-cml', + 'xcmp' => 'application/vnd.yellowriver-custom-menu', + 'xcmx' => 'image/x-cmx', + 'xcom' => 'application/x-msdownload', + 'xconf' => 'text/plain', + 'xcpio' => 'application/x-cpio', + + 'xcpt' => 'application/mac-compactpro', + 'xcrd' => 'application/x-mscardfile', + 'xcrl' => 'application/pkix-crl', + 'xcrt' => 'application/x-x509-ca-cert', + 'xcsh' => 'application/x-csh', + 'xcsml' => 'chemical/x-csml', + 'xcss' => 'text/css', + 'xcsv' => 'text/csv', + 'xcurl' => 'application/vnd.curl', + + 'xcww' => 'application/prs.cww', + 'xdaf' => 'application/vnd.mobius.daf', + 'xdavmount' => 'application/davmount+xml', + 'xdd2' => 'application/vnd.oma.dd2+xml', + 'xddd' => 'application/vnd.fujixerox.ddd', + 'xdef' => 'text/plain', + 'xder' => 'application/x-x509-ca-cert', + 'xdfac' => 'application/vnd.dreamfactory', + 'xdis' => 'application/vnd.mobius.dis', + + 'xdjv' => 'image/vnd.djvu', + 'xdjvu' => 'image/vnd.djvu', + 'xdll' => 'application/x-msdownload', + 'xdna' => 'application/vnd.dna', + 'xdoc' => 'application/msword', + 'xdot' => 'application/msword', + 'xdp' => 'application/vnd.osgi.dp', + 'xdpg' => 'application/vnd.dpgraph', + 'xdsc' => 'text/prs.lines.tag', + + 'xdtd' => 'application/xml-dtd', + 'xdvi' => 'application/x-dvi', + 'xdwf' => 'model/vnd.dwf', + 'xdwg' => 'image/vnd.dwg', + 'xdxf' => 'image/vnd.dxf', + 'xdxp' => 'application/vnd.spotfire.dxp', + 'xecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'xecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'xecelp9600' => 'audio/vnd.nuera.ecelp9600', + + 'xecma' => 'application/ecmascript', + 'xedm' => 'application/vnd.novadigm.edm', + 'xedx' => 'application/vnd.novadigm.edx', + 'xefif' => 'application/vnd.picsel', + 'xei6' => 'application/vnd.pg.osasli', + 'xeml' => 'message/rfc822', + 'xeol' => 'audio/vnd.digital-winds', + 'xeot' => 'application/vnd.ms-fontobject', + 'xeps' => 'application/postscript', + + 'xesf' => 'application/vnd.epson.esf', + 'xetx' => 'text/x-setext', + 'xexe' => 'application/x-msdownload', + 'xext' => 'application/vnd.novadigm.ext', + 'xez' => 'application/andrew-inset', + 'xez2' => 'application/vnd.ezpix-album', + 'xez3' => 'application/vnd.ezpix-package', + 'xfbs' => 'image/vnd.fastbidsheet', + 'xfdf' => 'application/vnd.fdf', + + 'xfe_launch' => 'application/vnd.denovo.fcselayout-link', + 'xfg5' => 'application/vnd.fujitsu.oasysgp', + 'xfli' => 'video/x-fli', + 'xflo' => 'application/vnd.micrografx.flo', + 'xflw' => 'application/vnd.kde.kivio', + 'xflx' => 'text/vnd.fmi.flexstor', + 'xfly' => 'text/vnd.fly', + 'xfnc' => 'application/vnd.frogans.fnc', + 'xfpx' => 'image/vnd.fpx', + + 'xfsc' => 'application/vnd.fsc.weblaunch', + 'xfst' => 'image/vnd.fst', + 'xftc' => 'application/vnd.fluxtime.clip', + 'xfti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'xfvt' => 'video/vnd.fvt', + 'xfzs' => 'application/vnd.fuzzysheet', + 'xg3' => 'image/g3fax', + 'xgac' => 'application/vnd.groove-account', + 'xgdl' => 'model/vnd.gdl', + + 'xghf' => 'application/vnd.groove-help', + 'xgif' => 'image/gif', + 'xgim' => 'application/vnd.groove-identity-message', + 'xgph' => 'application/vnd.flographit', + 'xgram' => 'application/srgs', + 'xgrv' => 'application/vnd.groove-injector', + 'xgrxml' => 'application/srgs+xml', + 'xgtar' => 'application/x-gtar', + 'xgtm' => 'application/vnd.groove-tool-message', + + 'xgtw' => 'model/vnd.gtw', + 'xh261' => 'video/h261', + 'xh263' => 'video/h263', + 'xh264' => 'video/h264', + 'xhbci' => 'application/vnd.hbci', + 'xhdf' => 'application/x-hdf', + 'xhlp' => 'application/winhlp', + 'xhpgl' => 'application/vnd.hp-hpgl', + 'xhpid' => 'application/vnd.hp-hpid', + + 'xhps' => 'application/vnd.hp-hps', + 'xhqx' => 'application/mac-binhex40', + 'xhtke' => 'application/vnd.kenameaapp', + 'xhtm' => 'text/html', + 'xhtml' => 'text/html', + 'xhvd' => 'application/vnd.yamaha.hv-dic', + 'xhvp' => 'application/vnd.yamaha.hv-voice', + 'xhvs' => 'application/vnd.yamaha.hv-script', + 'xice' => '#x-conference/x-cooltalk', + + 'xico' => 'image/x-icon', + 'xics' => 'text/calendar', + 'xief' => 'image/ief', + 'xifb' => 'text/calendar', + 'xifm' => 'application/vnd.shana.informed.formdata', + 'xigl' => 'application/vnd.igloader', + 'xigx' => 'application/vnd.micrografx.igx', + 'xiif' => 'application/vnd.shana.informed.interchange', + 'ximp' => 'application/vnd.accpac.simply.imp', + + 'xims' => 'application/vnd.ms-ims', + 'xin' => 'text/plain', + 'xipk' => 'application/vnd.shana.informed.package', + 'xirm' => 'application/vnd.ibm.rights-management', + 'xirp' => 'application/vnd.irepository.package+xml', + 'xitp' => 'application/vnd.shana.informed.formtemplate', + 'xivp' => 'application/vnd.immervision-ivp', + 'xivu' => 'application/vnd.immervision-ivu', + 'xjad' => 'text/vnd.sun.j2me.app-descriptor', + + 'xjam' => 'application/vnd.jam', + 'xjava' => 'text/x-java-source', + 'xjisp' => 'application/vnd.jisp', + 'xjlt' => 'application/vnd.hp-jlyt', + 'xjoda' => 'application/vnd.joost.joda-archive', + 'xjpe' => 'image/jpeg', + 'xjpeg' => 'image/jpeg', + 'xjpg' => 'image/jpeg', + 'xjpgm' => 'video/jpm', + + 'xjpgv' => 'video/jpeg', + 'xjpm' => 'video/jpm', + 'xjs' => 'application/javascript', + 'xjson' => 'application/json', + 'xkar' => 'audio/midi', + 'xkarbon' => 'application/vnd.kde.karbon', + 'xkfo' => 'application/vnd.kde.kformula', + 'xkia' => 'application/vnd.kidspiration', + 'xkml' => 'application/vnd.google-earth.kml+xml', + + 'xkmz' => 'application/vnd.google-earth.kmz', + 'xkon' => 'application/vnd.kde.kontour', + 'xksp' => 'application/vnd.kde.kspread', + 'xlatex' => 'application/x-latex', + 'xlbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'xlbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'xles' => 'application/vnd.hhe.lesson-player', + 'xlist' => 'text/plain', + 'xlog' => 'text/plain', + + 'xlrm' => 'application/vnd.ms-lrm', + 'xltf' => 'application/vnd.frogans.ltf', + 'xlvp' => 'audio/vnd.lucent.voice', + 'xlwp' => 'application/vnd.lotus-wordpro', + 'xm13' => 'application/x-msmediaview', + 'xm14' => 'application/x-msmediaview', + 'xm1v' => 'video/mpeg', + 'xm2a' => 'audio/mpeg', + 'xm3a' => 'audio/mpeg', + + 'xm3u' => 'audio/x-mpegurl', + 'xm4u' => 'video/vnd.mpegurl', + 'xmag' => 'application/vnd.ecowin.chart', + 'xmathml' => 'application/mathml+xml', + 'xmbk' => 'application/vnd.mobius.mbk', + 'xmbox' => 'application/mbox', + 'xmc1' => 'application/vnd.medcalcdata', + 'xmcd' => 'application/vnd.mcd', + 'xmdb' => 'application/x-msaccess', + + 'xmdi' => 'image/vnd.ms-modi', + 'xmesh' => 'model/mesh', + 'xmfm' => 'application/vnd.mfmp', + 'xmgz' => 'application/vnd.proteus.magazine', + 'xmid' => 'audio/midi', + 'xmidi' => 'audio/midi', + 'xmif' => 'application/vnd.mif', + 'xmime' => 'message/rfc822', + 'xmj2' => 'video/mj2', + + 'xmjp2' => 'video/mj2', + 'xmlp' => 'application/vnd.dolby.mlp', + 'xmmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'xmmf' => 'application/vnd.smaf', + 'xmmr' => 'image/vnd.fujixerox.edmics-mmr', + 'xmny' => 'application/x-msmoney', + 'xmov' => 'video/quicktime', + 'xmovie' => 'video/x-sgi-movie', + 'xmp2' => 'audio/mpeg', + + 'xmp2a' => 'audio/mpeg', + 'xmp3' => 'audio/mpeg', + 'xmp4' => 'video/mp4', + 'xmp4a' => 'audio/mp4', + 'xmp4s' => 'application/mp4', + 'xmp4v' => 'video/mp4', + 'xmpc' => 'application/vnd.mophun.certificate', + 'xmpe' => 'video/mpeg', + 'xmpeg' => 'video/mpeg', + + 'xmpg' => 'video/mpeg', + 'xmpg4' => 'video/mp4', + 'xmpga' => 'audio/mpeg', + 'xmpkg' => 'application/vnd.apple.installer+xml', + 'xmpm' => 'application/vnd.blueice.multipass', + 'xmpn' => 'application/vnd.mophun.application', + 'xmpp' => 'application/vnd.ms-project', + 'xmpt' => 'application/vnd.ms-project', + 'xmpy' => 'application/vnd.ibm.minipay', + + 'xmqy' => 'application/vnd.mobius.mqy', + 'xmrc' => 'application/marc', + 'xmscml' => 'application/mediaservercontrol+xml', + 'xmseq' => 'application/vnd.mseq', + 'xmsf' => 'application/vnd.epson.msf', + 'xmsh' => 'model/mesh', + 'xmsi' => 'application/x-msdownload', + 'xmsl' => 'application/vnd.mobius.msl', + 'xmsty' => 'application/vnd.muvee.style', + + 'xmts' => 'model/vnd.mts', + 'xmus' => 'application/vnd.musician', + 'xmvb' => 'application/x-msmediaview', + 'xmwf' => 'application/vnd.mfer', + 'xmxf' => 'application/mxf', + 'xmxl' => 'application/vnd.recordare.musicxml', + 'xmxml' => 'application/xv+xml', + 'xmxs' => 'application/vnd.triscape.mxs', + 'xmxu' => 'video/vnd.mpegurl', + + 'xn-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'xngdat' => 'application/vnd.nokia.n-gage.data', + 'xnlu' => 'application/vnd.neurolanguage.nlu', + 'xnml' => 'application/vnd.enliven', + 'xnnd' => 'application/vnd.noblenet-directory', + 'xnns' => 'application/vnd.noblenet-sealer', + 'xnnw' => 'application/vnd.noblenet-web', + 'xnpx' => 'image/vnd.net-fpx', + 'xnsf' => 'application/vnd.lotus-notes', + + 'xoa2' => 'application/vnd.fujitsu.oasys2', + 'xoa3' => 'application/vnd.fujitsu.oasys3', + 'xoas' => 'application/vnd.fujitsu.oasys', + 'xobd' => 'application/x-msbinder', + 'xoda' => 'application/oda', + 'xodc' => 'application/vnd.oasis.opendocument.chart', + 'xodf' => 'application/vnd.oasis.opendocument.formula', + 'xodg' => 'application/vnd.oasis.opendocument.graphics', + 'xodi' => 'application/vnd.oasis.opendocument.image', + + 'xodp' => 'application/vnd.oasis.opendocument.presentation', + 'xods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'xodt' => 'application/vnd.oasis.opendocument.text', + 'xogg' => 'application/ogg', + 'xoprc' => 'application/vnd.palm', + 'xorg' => 'application/vnd.lotus-organizer', + 'xotc' => 'application/vnd.oasis.opendocument.chart-template', + 'xotf' => 'application/vnd.oasis.opendocument.formula-template', + 'xotg' => 'application/vnd.oasis.opendocument.graphics-template', + + 'xoth' => 'application/vnd.oasis.opendocument.text-web', + 'xoti' => 'application/vnd.oasis.opendocument.image-template', + 'xotm' => 'application/vnd.oasis.opendocument.text-master', + 'xots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'xott' => 'application/vnd.oasis.opendocument.text-template', + 'xoxt' => 'application/vnd.openofficeorg.extension', + 'xp10' => 'application/pkcs10', + 'xp7r' => 'application/x-pkcs7-certreqresp', + 'xp7s' => 'application/pkcs7-signature', + + 'xpbd' => 'application/vnd.powerbuilder6', + 'xpbm' => 'image/x-portable-bitmap', + 'xpcl' => 'application/vnd.hp-pcl', + 'xpclxl' => 'application/vnd.hp-pclxl', + 'xpct' => 'image/x-pict', + 'xpcx' => 'image/x-pcx', + 'xpdb' => 'chemical/x-pdb', + 'xpdf' => 'application/pdf', + 'xpfr' => 'application/font-tdpfr', + + 'xpgm' => 'image/x-portable-graymap', + 'xpgn' => 'application/x-chess-pgn', + 'xpgp' => 'application/pgp-encrypted', + 'xpic' => 'image/x-pict', + 'xpki' => 'application/pkixcmp', + 'xpkipath' => 'application/pkix-pkipath', + 'xplb' => 'application/vnd.3gpp.pic-bw-large', + 'xplc' => 'application/vnd.mobius.plc', + 'xplf' => 'application/vnd.pocketlearn', + + 'xpls' => 'application/pls+xml', + 'xpml' => 'application/vnd.ctc-posml', + 'xpng' => 'image/png', + 'xpnm' => 'image/x-portable-anymap', + 'xportpkg' => 'application/vnd.macports.portpkg', + 'xpot' => 'application/vnd.ms-powerpoint', + 'xppd' => 'application/vnd.cups-ppd', + 'xppm' => 'image/x-portable-pixmap', + 'xpps' => 'application/vnd.ms-powerpoint', + + 'xppt' => 'application/vnd.ms-powerpoint', + 'xpqa' => 'application/vnd.palm', + 'xprc' => 'application/vnd.palm', + 'xpre' => 'application/vnd.lotus-freelance', + 'xprf' => 'application/pics-rules', + 'xps' => 'application/postscript', + 'xpsb' => 'application/vnd.3gpp.pic-bw-small', + 'xpsd' => 'image/vnd.adobe.photoshop', + 'xptid' => 'application/vnd.pvi.ptid1', + + 'xpub' => 'application/x-mspublisher', + 'xpvb' => 'application/vnd.3gpp.pic-bw-var', + 'xpwn' => 'application/vnd.3m.post-it-notes', + 'xqam' => 'application/vnd.epson.quickanime', + 'xqbo' => 'application/vnd.intu.qbo', + 'xqfx' => 'application/vnd.intu.qfx', + 'xqps' => 'application/vnd.publishare-delta-tree', + 'xqt' => 'video/quicktime', + 'xra' => 'audio/x-pn-realaudio', + + 'xram' => 'audio/x-pn-realaudio', + 'xrar' => 'application/x-rar-compressed', + 'xras' => 'image/x-cmu-raster', + 'xrcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'xrdf' => 'application/rdf+xml', + 'xrdz' => 'application/vnd.data-vision.rdz', + 'xrep' => 'application/vnd.businessobjects', + 'xrgb' => 'image/x-rgb', + 'xrif' => 'application/reginfo+xml', + + 'xrl' => 'application/resource-lists+xml', + 'xrlc' => 'image/vnd.fujixerox.edmics-rlc', + 'xrm' => 'application/vnd.rn-realmedia', + 'xrmi' => 'audio/midi', + 'xrmp' => 'audio/x-pn-realaudio-plugin', + 'xrms' => 'application/vnd.jcp.javame.midlet-rms', + 'xrnc' => 'application/relax-ng-compact-syntax', + 'xrpss' => 'application/vnd.nokia.radio-presets', + 'xrpst' => 'application/vnd.nokia.radio-preset', + + 'xrq' => 'application/sparql-query', + 'xrs' => 'application/rls-services+xml', + 'xrsd' => 'application/rsd+xml', + 'xrss' => 'application/rss+xml', + 'xrtf' => 'application/rtf', + 'xrtx' => 'text/richtext', + 'xsaf' => 'application/vnd.yamaha.smaf-audio', + 'xsbml' => 'application/sbml+xml', + 'xsc' => 'application/vnd.ibm.secure-container', + + 'xscd' => 'application/x-msschedule', + 'xscm' => 'application/vnd.lotus-screencam', + 'xscq' => 'application/scvp-cv-request', + 'xscs' => 'application/scvp-cv-response', + 'xsdp' => 'application/sdp', + 'xsee' => 'application/vnd.seemail', + 'xsema' => 'application/vnd.sema', + 'xsemd' => 'application/vnd.semd', + 'xsemf' => 'application/vnd.semf', + + 'xsetpay' => 'application/set-payment-initiation', + 'xsetreg' => 'application/set-registration-initiation', + 'xsfs' => 'application/vnd.spotfire.sfs', + 'xsgm' => 'text/sgml', + 'xsgml' => 'text/sgml', + 'xsh' => 'application/x-sh', + 'xshar' => 'application/x-shar', + 'xshf' => 'application/shf+xml', + 'xsilo' => 'model/mesh', + + 'xsit' => 'application/x-stuffit', + 'xsitx' => 'application/x-stuffitx', + 'xslt' => 'application/vnd.epson.salt', + 'xsnd' => 'audio/basic', + 'xspf' => 'application/vnd.yamaha.smaf-phrase', + 'xspl' => 'application/x-futuresplash', + 'xspot' => 'text/vnd.in3d.spot', + 'xspp' => 'application/scvp-vp-response', + 'xspq' => 'application/scvp-vp-request', + + 'xsrc' => 'application/x-wais-source', + 'xsrx' => 'application/sparql-results+xml', + 'xssf' => 'application/vnd.epson.ssf', + 'xssml' => 'application/ssml+xml', + 'xstf' => 'application/vnd.wt.stf', + 'xstk' => 'application/hyperstudio', + 'xstr' => 'application/vnd.pg.format', + 'xsus' => 'application/vnd.sus-calendar', + 'xsusp' => 'application/vnd.sus-calendar', + + 'xsv4cpio' => 'application/x-sv4cpio', + 'xsv4crc' => 'application/x-sv4crc', + 'xsvd' => 'application/vnd.svd', + 'xswf' => 'application/x-shockwave-flash', + 'xtao' => 'application/vnd.tao.intent-module-archive', + 'xtar' => 'application/x-tar', + 'xtcap' => 'application/vnd.3gpp2.tcap', + 'xtcl' => 'application/x-tcl', + 'xtex' => 'application/x-tex', + + 'xtext' => 'text/plain', + 'xtif' => 'image/tiff', + 'xtiff' => 'image/tiff', + 'xtmo' => 'application/vnd.tmobile-livetv', + 'xtorrent' => 'application/x-bittorrent', + 'xtpl' => 'application/vnd.groove-tool-template', + 'xtpt' => 'application/vnd.trid.tpt', + 'xtra' => 'application/vnd.trueapp', + 'xtrm' => 'application/x-msterminal', + + 'xtsv' => 'text/tab-separated-values', + 'xtxd' => 'application/vnd.genomatix.tuxedo', + 'xtxf' => 'application/vnd.mobius.txf', + 'xtxt' => 'text/plain', + 'xumj' => 'application/vnd.umajin', + 'xunityweb' => 'application/vnd.unity', + 'xuoml' => 'application/vnd.uoml+xml', + 'xuri' => 'text/uri-list', + 'xuris' => 'text/uri-list', + + 'xurls' => 'text/uri-list', + 'xustar' => 'application/x-ustar', + 'xutz' => 'application/vnd.uiq.theme', + 'xuu' => 'text/x-uuencode', + 'xvcd' => 'application/x-cdlink', + 'xvcf' => 'text/x-vcard', + 'xvcg' => 'application/vnd.groove-vcard', + 'xvcs' => 'text/x-vcalendar', + 'xvcx' => 'application/vnd.vcx', + + 'xvis' => 'application/vnd.visionary', + 'xviv' => 'video/vnd.vivo', + 'xvrml' => 'model/vrml', + 'xvsd' => 'application/vnd.visio', + 'xvsf' => 'application/vnd.vsf', + 'xvss' => 'application/vnd.visio', + 'xvst' => 'application/vnd.visio', + 'xvsw' => 'application/vnd.visio', + 'xvtu' => 'model/vnd.vtu', + + 'xvxml' => 'application/voicexml+xml', + 'xwav' => 'audio/x-wav', + 'xwax' => 'audio/x-ms-wax', + 'xwbmp' => 'image/vnd.wap.wbmp', + 'xwbs' => 'application/vnd.criticaltools.wbs+xml', + 'xwbxml' => 'application/vnd.wap.wbxml', + 'xwcm' => 'application/vnd.ms-works', + 'xwdb' => 'application/vnd.ms-works', + 'xwks' => 'application/vnd.ms-works', + + 'xwm' => 'video/x-ms-wm', + 'xwma' => 'audio/x-ms-wma', + 'xwmd' => 'application/x-ms-wmd', + 'xwmf' => 'application/x-msmetafile', + 'xwml' => 'text/vnd.wap.wml', + 'xwmlc' => 'application/vnd.wap.wmlc', + 'xwmls' => 'text/vnd.wap.wmlscript', + 'xwmlsc' => 'application/vnd.wap.wmlscriptc', + 'xwmv' => 'video/x-ms-wmv', + + 'xwmx' => 'video/x-ms-wmx', + 'xwmz' => 'application/x-ms-wmz', + 'xwpd' => 'application/vnd.wordperfect', + 'xwpl' => 'application/vnd.ms-wpl', + 'xwps' => 'application/vnd.ms-works', + 'xwqd' => 'application/vnd.wqd', + 'xwri' => 'application/x-mswrite', + 'xwrl' => 'model/vrml', + 'xwsdl' => 'application/wsdl+xml', + + 'xwspolicy' => 'application/wspolicy+xml', + 'xwtb' => 'application/vnd.webturbo', + 'xwvx' => 'video/x-ms-wvx', + 'xx3d' => 'application/vnd.hzn-3d-crossword', + 'xxar' => 'application/vnd.xara', + 'xxbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xxbm' => 'image/x-xbitmap', + 'xxdm' => 'application/vnd.syncml.dm+xml', + 'xxdp' => 'application/vnd.adobe.xdp+xml', + + 'xxdw' => 'application/vnd.fujixerox.docuworks', + 'xxenc' => 'application/xenc+xml', + 'xxfdf' => 'application/vnd.adobe.xfdf', + 'xxfdl' => 'application/vnd.xfdl', + 'xxht' => 'application/xhtml+xml', + 'xxhtml' => 'application/xhtml+xml', + 'xxhvml' => 'application/xv+xml', + 'xxif' => 'image/vnd.xiff', + 'xxla' => 'application/vnd.ms-excel', + + 'xxlc' => 'application/vnd.ms-excel', + 'xxlm' => 'application/vnd.ms-excel', + 'xxls' => 'application/vnd.ms-excel', + 'xxlt' => 'application/vnd.ms-excel', + 'xxlw' => 'application/vnd.ms-excel', + 'xxml' => 'application/xml', + 'xxo' => 'application/vnd.olpc-sugar', + 'xxop' => 'application/xop+xml', + 'xxpm' => 'image/x-xpixmap', + + 'xxpr' => 'application/vnd.is-xpr', + 'xxps' => 'application/vnd.ms-xpsdocument', + 'xxsl' => 'application/xml', + 'xxslt' => 'application/xslt+xml', + 'xxsm' => 'application/vnd.syncml+xml', + 'xxspf' => 'application/xspf+xml', + 'xxul' => 'application/vnd.mozilla.xul+xml', + 'xxvm' => 'application/xv+xml', + 'xxvml' => 'application/xv+xml', + + 'xxwd' => 'image/x-xwindowdump', + 'xxyz' => 'chemical/x-xyz', + 'xzaz' => 'application/vnd.zzazz.deck+xml', + 'xzip' => 'application/zip', + 'xzmm' => 'application/vnd.handheld-entertainment+xml' + ); } diff --git a/app/code/core/Mage/Downloadable/Model/Mysql4/Indexer/Price.php b/app/code/core/Mage/Downloadable/Model/Mysql4/Indexer/Price.php new file mode 100644 index 0000000000..dd39df9d96 --- /dev/null +++ b/app/code/core/Mage/Downloadable/Model/Mysql4/Indexer/Price.php @@ -0,0 +1,171 @@ + + */ +class Mage_Downloadable_Model_Mysql4_Indexer_Price + extends Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default +{ + /** + * Reindex temporary (price result data) for all products + * + * @return Mage_Downloadable_Model_Mysql4_Indexer_Price + */ + public function reindexAll() + { + $this->_prepareFinalPriceData(); + $this->_applyCustomOption(); + $this->_applyDownloadableLink(); + $this->_movePriceDataToIndexTable(); + + return $this; + } + + /** + * Reindex temporary (price result data) for defined product(s) + * + * @param int|array $entityIds + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface + */ + public function reindexEntity($entityIds) + { + $this->_prepareFinalPriceData($entityIds); + $this->_applyCustomOption(); + $this->_applyDownloadableLink(); + $this->_movePriceDataToIndexTable(); + + return $this; + } + + /** + * Retrieve downloadable links price temporary index table name + * + * @see _prepareDefaultFinalPriceTable() + * @return string + */ + protected function _getDownloadableLinkPriceTable() + { + return $this->getIdxTable($this->getMainTable() . '_downloadable'); + } + + /** + * Prepare downloadable links price temporary index table + * + * @return Mage_Downloadable_Model_Mysql4_Indexer_Price + */ + protected function _prepareDownloadableLinkPriceTable() + { + $write = $this->_getWriteAdapter(); + $table = $this->_getDownloadableLinkPriceTable(); + + $query = sprintf('DROP TABLE IF EXISTS %s', $write->quoteIdentifier($table)); + $write->query($query); + + $query = sprintf('CREATE TABLE %s (' + . ' `entity_id` INT(10) UNSIGNED NOT NULL,' + . ' `customer_group_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `website_id` SMALLINT(5) UNSIGNED NOT NULL,' + . ' `min_price` DECIMAL(12,4) DEFAULT NULL,' + . ' `max_price` DECIMAL(12,4) DEFAULT NULL,' + . ' PRIMARY KEY (`entity_id`,`customer_group_id`,`website_id`)' + . ') ENGINE=MYISAM DEFAULT CHARSET=utf8', + $write->quoteIdentifier($table)); + $write->query($query); + + return $this; + } + + /** + * Calculate and apply Downloadable links price to index + * + * @return Mage_Downloadable_Model_Mysql4_Indexer_Price + */ + protected function _applyDownloadableLink() + { + $write = $this->_getWriteAdapter(); + $table = $this->_getDownloadableLinkPriceTable(); + + $this->_prepareDownloadableLinkPriceTable(); + + $dlType = $this->_getAttribute('links_purchased_separately'); + + $select = $write->select() + ->from( + array('i' => $this->_getDefaultFinalPriceTable()), + array('entity_id', 'customer_group_id', 'website_id')) + ->join( + array('dl' => $dlType->getBackend()->getTable()), + "dl.entity_id = i.entity_id AND dl.attribute_id = {$dlType->getAttributeId()}" + . " AND dl.store_id = 0", + array()) + ->join( + array('dll' => $this->getTable('downloadable/link')), + 'dll.product_id = i.entity_id', + array()) + ->join( + array('dlpd' => $this->getTable('downloadable/link_price')), + 'dll.link_id = dlpd.link_id AND dlpd.website_id = 0', + array()) + ->joinLeft( + array('dlpw' => $this->getTable('downloadable/link_price')), + 'dlpd.link_id = dlpw.link_id AND dlpw.website_id = i.website_id', + array()) + ->where('dl.value = ?', 1) + ->group(array('i.entity_id', 'i.customer_group_id', 'i.website_id')) + ->columns(array( + 'min_price' => new Zend_Db_Expr('MIN(IF(dlpw.price_id, dlpw.price, dlpd.price))'), + 'max_price' => new Zend_Db_Expr('SUM(IF(dlpw.price_id, dlpw.price, dlpd.price))') + )); + + $query = $select->insertFromSelect($table); + $write->query($query); + + $select = $write->select() + ->join( + array('id' => $table), + 'i.entity_id = id.entity_id AND i.customer_group_id = id.customer_group_id' + .' AND i.website_id = id.website_id', + array()) + ->columns(array( + 'min_price' => new Zend_Db_Expr('i.min_price + id.min_price'), + 'max_price' => new Zend_Db_Expr('i.max_price + id.max_price'), + 'tier_price' => new Zend_Db_Expr('IF(i.tier_price IS NOT NULL, i.tier_price + id.min_price, NULL)') + )); + + $query = $select->crossUpdateFromSelect(array('i' => $this->_getDefaultFinalPriceTable())); + $write->query($query); + + $write->truncate($table); + + return $this; + } +} diff --git a/app/code/core/Mage/Downloadable/Model/Product/Type.php b/app/code/core/Mage/Downloadable/Model/Product/Type.php index 3dac7c132c..39fe36f079 100644 --- a/app/code/core/Mage/Downloadable/Model/Product/Type.php +++ b/app/code/core/Mage/Downloadable/Model/Product/Type.php @@ -70,6 +70,9 @@ public function getLinks($product = null) */ public function hasLinks($product = null) { + if ($this->getProduct($product)->hasData('links_exist')) { + return $this->getProduct($product)->getData('links_exist'); + } return count($this->getLinks($product)) > 0; } @@ -143,7 +146,7 @@ public function hasSamples($product = null) } /** - * Enter description here... + * Save Product downloadable information (links and samples) * * @param Mage_Catalog_Model_Product $product * @return Mage_Downloadable_Model_Product_Type @@ -260,6 +263,9 @@ public function save($product = null) if ($_deleteItems) { Mage::getResourceModel('downloadable/link')->deleteItems($_deleteItems); } + if ($this->getProduct($product)->getLinksPurchasedSeparately()) { + $this->getProduct($product)->setIsCustomOptionChanged(); + } } } @@ -267,7 +273,7 @@ public function save($product = null) } /** - * Enter description here... + * Prepare Product object before adding to Shopping Cart * * @param Varien_Object $buyRequest * @param Mage_Catalog_Model_Product $product @@ -355,6 +361,20 @@ public function beforeSave($product = null) $this->getProduct($product)->setTypeHasOptions(false); $this->getProduct($product)->setTypeHasRequiredOptions(false); } + + // Update links_exist attribute value + $linksExist = false; + if ($data = $product->getDownloadableData()) { + if (isset($data['link'])) { + foreach ($data['link'] as $linkItem) { + if (!isset($linkItem['is_delete']) || !$linkItem['is_delete']) { + $linksExist = true; + break; + } + } + } + } + $this->getProduct($product)->setLinksExist($linksExist); } /** @@ -383,4 +403,15 @@ public function getSearchableData($product = null) return $searchData; } + + /** + * Check is product available for sale + * + * @param Mage_Catalog_Model_Product $product + * @return bool + */ + public function isSalable($product = null) + { + return $this->hasLinks($product) && parent::isSalable($product); + } } diff --git a/app/code/core/Mage/Downloadable/etc/adminhtml.xml b/app/code/core/Mage/Downloadable/etc/adminhtml.xml new file mode 100644 index 0000000000..9c0985e505 --- /dev/null +++ b/app/code/core/Mage/Downloadable/etc/adminhtml.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + Downloadable product Section + + + + + + + + + + diff --git a/app/code/core/Mage/Downloadable/etc/config.xml b/app/code/core/Mage/Downloadable/etc/config.xml index 62bf6b870d..cfce6421fa 100644 --- a/app/code/core/Mage/Downloadable/etc/config.xml +++ b/app/code/core/Mage/Downloadable/etc/config.xml @@ -28,7 +28,7 @@ - 0.1.15 + 0.1.16 @@ -108,20 +108,7 @@ Mage_Downloadable Mage_Catalog_Model_Resource_Eav_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - @@ -133,6 +120,7 @@ downloadable/product_price downloadable/catalogIndex_data_downloadable 0 + downloadable/indexer_price @@ -160,605 +148,6 @@ downloadable/sales_order_pdf_items_creditmemo - - - application/vnd.lotus-1-2-3 - text/vnd.in3d.3dml - video/3gpp2 - video/3gpp - application/x-ace-compressed - application/vnd.acucobol - application/vnd.audiograph - application/postscript - audio/x-aiff - - audio/x-aiff - audio/x-aiff - application/vnd.amiga.ami - application/vnd.lotus-approach - video/x-ms-asf - application/vnd.accpac.simply.aso - video/x-ms-asf - application/atom+xml - application/atomcat+xml - - application/atomsvc+xml - application/vnd.antix.game-component - audio/basic - video/x-msvideo - application/x-msdownload - application/x-bcpio - application/vnd.syncml.dm+wbxml - application/vnd.fujitsu.oasysprs - application/vnd.bmi - - image/bmp - application/vnd.previewsystems.box - application/x-bzip2 - image/prs.btif - application/x-bzip - application/x-bzip2 - application/vnd.ms-cab-compressed - application/ccxml+xml - application/vnd.contact.cmsg - - application/vnd.mediastation.cdkey - chemical/x-cdx - application/vnd.chemdraw+xml - application/vnd.cinderella - application/pkix-cert - image/cgm - application/x-chat - application/vnd.ms-htmlhelp - application/vnd.kde.kchart - - chemical/x-cif - application/vnd.anser-web-certificate-issue-initiation - application/vnd.ms-artgalry - application/vnd.claymore - application/vnd.crick.clicker.keyboard - application/vnd.crick.clicker.palette - application/vnd.crick.clicker.template - application/vnd.crick.clicker.wordbank - application/vnd.crick.clicker - - application/x-msclip - application/vnd.cosmocaller - chemical/x-cmdf - chemical/x-cml - application/vnd.yellowriver-custom-menu - image/x-cmx - application/x-msdownload - text/plain - application/x-cpio - - application/mac-compactpro - application/x-mscardfile - application/pkix-crl - application/x-x509-ca-cert - application/x-csh - chemical/x-csml - text/css - text/csv - application/vnd.curl - - application/prs.cww - application/vnd.mobius.daf - application/davmount+xml - application/vnd.oma.dd2+xml - application/vnd.fujixerox.ddd - text/plain - application/x-x509-ca-cert - application/vnd.dreamfactory - application/vnd.mobius.dis - - image/vnd.djvu - image/vnd.djvu - application/x-msdownload - application/vnd.dna - application/msword - application/msword - application/vnd.osgi.dp - application/vnd.dpgraph - text/prs.lines.tag - - application/xml-dtd - application/x-dvi - model/vnd.dwf - image/vnd.dwg - image/vnd.dxf - application/vnd.spotfire.dxp - audio/vnd.nuera.ecelp4800 - audio/vnd.nuera.ecelp7470 - audio/vnd.nuera.ecelp9600 - - application/ecmascript - application/vnd.novadigm.edm - application/vnd.novadigm.edx - application/vnd.picsel - application/vnd.pg.osasli - message/rfc822 - audio/vnd.digital-winds - application/vnd.ms-fontobject - application/postscript - - application/vnd.epson.esf - text/x-setext - application/x-msdownload - application/vnd.novadigm.ext - application/andrew-inset - application/vnd.ezpix-album - application/vnd.ezpix-package - image/vnd.fastbidsheet - application/vnd.fdf - - application/vnd.denovo.fcselayout-link - application/vnd.fujitsu.oasysgp - video/x-fli - application/vnd.micrografx.flo - application/vnd.kde.kivio - text/vnd.fmi.flexstor - text/vnd.fly - application/vnd.frogans.fnc - image/vnd.fpx - - application/vnd.fsc.weblaunch - image/vnd.fst - application/vnd.fluxtime.clip - application/vnd.anser-web-funds-transfer-initiation - video/vnd.fvt - application/vnd.fuzzysheet - image/g3fax - application/vnd.groove-account - model/vnd.gdl - - application/vnd.groove-help - image/gif - application/vnd.groove-identity-message - application/vnd.flographit - application/srgs - application/vnd.groove-injector - application/srgs+xml - application/x-gtar - application/vnd.groove-tool-message - - model/vnd.gtw - video/h261 - video/h263 - video/h264 - application/vnd.hbci - application/x-hdf - application/winhlp - application/vnd.hp-hpgl - application/vnd.hp-hpid - - application/vnd.hp-hps - application/mac-binhex40 - application/vnd.kenameaapp - text/html - text/html - application/vnd.yamaha.hv-dic - application/vnd.yamaha.hv-voice - application/vnd.yamaha.hv-script - #x-conference/x-cooltalk - - image/x-icon - text/calendar - image/ief - text/calendar - application/vnd.shana.informed.formdata - application/vnd.igloader - application/vnd.micrografx.igx - application/vnd.shana.informed.interchange - application/vnd.accpac.simply.imp - - application/vnd.ms-ims - text/plain - application/vnd.shana.informed.package - application/vnd.ibm.rights-management - application/vnd.irepository.package+xml - application/vnd.shana.informed.formtemplate - application/vnd.immervision-ivp - application/vnd.immervision-ivu - text/vnd.sun.j2me.app-descriptor - - application/vnd.jam - text/x-java-source - application/vnd.jisp - application/vnd.hp-jlyt - application/vnd.joost.joda-archive - image/jpeg - image/jpeg - image/jpeg - video/jpm - - video/jpeg - video/jpm - application/javascript - application/json - audio/midi - application/vnd.kde.karbon - application/vnd.kde.kformula - application/vnd.kidspiration - application/vnd.google-earth.kml+xml - - application/vnd.google-earth.kmz - application/vnd.kde.kontour - application/vnd.kde.kspread - application/x-latex - application/vnd.llamagraphics.life-balance.desktop - application/vnd.llamagraphics.life-balance.exchange+xml - application/vnd.hhe.lesson-player - text/plain - text/plain - - application/vnd.ms-lrm - application/vnd.frogans.ltf - audio/vnd.lucent.voice - application/vnd.lotus-wordpro - application/x-msmediaview - application/x-msmediaview - video/mpeg - audio/mpeg - audio/mpeg - - audio/x-mpegurl - video/vnd.mpegurl - application/vnd.ecowin.chart - application/mathml+xml - application/vnd.mobius.mbk - application/mbox - application/vnd.medcalcdata - application/vnd.mcd - application/x-msaccess - - image/vnd.ms-modi - model/mesh - application/vnd.mfmp - application/vnd.proteus.magazine - audio/midi - audio/midi - application/vnd.mif - message/rfc822 - video/mj2 - - video/mj2 - application/vnd.dolby.mlp - application/vnd.chipnuts.karaoke-mmd - application/vnd.smaf - image/vnd.fujixerox.edmics-mmr - application/x-msmoney - video/quicktime - video/x-sgi-movie - audio/mpeg - - audio/mpeg - audio/mpeg - video/mp4 - audio/mp4 - application/mp4 - video/mp4 - application/vnd.mophun.certificate - video/mpeg - video/mpeg - - video/mpeg - video/mp4 - audio/mpeg - application/vnd.apple.installer+xml - application/vnd.blueice.multipass - application/vnd.mophun.application - application/vnd.ms-project - application/vnd.ms-project - application/vnd.ibm.minipay - - application/vnd.mobius.mqy - application/marc - application/mediaservercontrol+xml - application/vnd.mseq - application/vnd.epson.msf - model/mesh - application/x-msdownload - application/vnd.mobius.msl - application/vnd.muvee.style - - model/vnd.mts - application/vnd.musician - application/x-msmediaview - application/vnd.mfer - application/mxf - application/vnd.recordare.musicxml - application/xv+xml - application/vnd.triscape.mxs - video/vnd.mpegurl - - application/vnd.nokia.n-gage.symbian.install - application/vnd.nokia.n-gage.data - application/vnd.neurolanguage.nlu - application/vnd.enliven - application/vnd.noblenet-directory - application/vnd.noblenet-sealer - application/vnd.noblenet-web - image/vnd.net-fpx - application/vnd.lotus-notes - - application/vnd.fujitsu.oasys2 - application/vnd.fujitsu.oasys3 - application/vnd.fujitsu.oasys - application/x-msbinder - application/oda - application/vnd.oasis.opendocument.chart - application/vnd.oasis.opendocument.formula - application/vnd.oasis.opendocument.graphics - application/vnd.oasis.opendocument.image - - application/vnd.oasis.opendocument.presentation - application/vnd.oasis.opendocument.spreadsheet - application/vnd.oasis.opendocument.text - application/ogg - application/vnd.palm - application/vnd.lotus-organizer - application/vnd.oasis.opendocument.chart-template - application/vnd.oasis.opendocument.formula-template - application/vnd.oasis.opendocument.graphics-template - - application/vnd.oasis.opendocument.text-web - application/vnd.oasis.opendocument.image-template - application/vnd.oasis.opendocument.text-master - application/vnd.oasis.opendocument.spreadsheet-template - application/vnd.oasis.opendocument.text-template - application/vnd.openofficeorg.extension - application/pkcs10 - application/x-pkcs7-certreqresp - application/pkcs7-signature - - application/vnd.powerbuilder6 - image/x-portable-bitmap - application/vnd.hp-pcl - application/vnd.hp-pclxl - image/x-pict - image/x-pcx - chemical/x-pdb - application/pdf - application/font-tdpfr - - image/x-portable-graymap - application/x-chess-pgn - application/pgp-encrypted - image/x-pict - application/pkixcmp - application/pkix-pkipath - application/vnd.3gpp.pic-bw-large - application/vnd.mobius.plc - application/vnd.pocketlearn - - application/pls+xml - application/vnd.ctc-posml - image/png - image/x-portable-anymap - application/vnd.macports.portpkg - application/vnd.ms-powerpoint - application/vnd.cups-ppd - image/x-portable-pixmap - application/vnd.ms-powerpoint - - application/vnd.ms-powerpoint - application/vnd.palm - application/vnd.palm - application/vnd.lotus-freelance - application/pics-rules - application/postscript - application/vnd.3gpp.pic-bw-small - image/vnd.adobe.photoshop - application/vnd.pvi.ptid1 - - application/x-mspublisher - application/vnd.3gpp.pic-bw-var - application/vnd.3m.post-it-notes - application/vnd.epson.quickanime - application/vnd.intu.qbo - application/vnd.intu.qfx - application/vnd.publishare-delta-tree - video/quicktime - audio/x-pn-realaudio - - audio/x-pn-realaudio - application/x-rar-compressed - image/x-cmu-raster - application/vnd.ipunplugged.rcprofile - application/rdf+xml - application/vnd.data-vision.rdz - application/vnd.businessobjects - image/x-rgb - application/reginfo+xml - - application/resource-lists+xml - image/vnd.fujixerox.edmics-rlc - application/vnd.rn-realmedia - audio/midi - audio/x-pn-realaudio-plugin - application/vnd.jcp.javame.midlet-rms - application/relax-ng-compact-syntax - application/vnd.nokia.radio-presets - application/vnd.nokia.radio-preset - - application/sparql-query - application/rls-services+xml - application/rsd+xml - application/rss+xml - application/rtf - text/richtext - application/vnd.yamaha.smaf-audio - application/sbml+xml - application/vnd.ibm.secure-container - - application/x-msschedule - application/vnd.lotus-screencam - application/scvp-cv-request - application/scvp-cv-response - application/sdp - application/vnd.seemail - application/vnd.sema - application/vnd.semd - application/vnd.semf - - application/set-payment-initiation - application/set-registration-initiation - application/vnd.spotfire.sfs - text/sgml - text/sgml - application/x-sh - application/x-shar - application/shf+xml - model/mesh - - application/x-stuffit - application/x-stuffitx - application/vnd.epson.salt - audio/basic - application/vnd.yamaha.smaf-phrase - application/x-futuresplash - text/vnd.in3d.spot - application/scvp-vp-response - application/scvp-vp-request - - application/x-wais-source - application/sparql-results+xml - application/vnd.epson.ssf - application/ssml+xml - application/vnd.wt.stf - application/hyperstudio - application/vnd.pg.format - application/vnd.sus-calendar - application/vnd.sus-calendar - - application/x-sv4cpio - application/x-sv4crc - application/vnd.svd - application/x-shockwave-flash - application/vnd.tao.intent-module-archive - application/x-tar - application/vnd.3gpp2.tcap - application/x-tcl - application/x-tex - - text/plain - image/tiff - image/tiff - application/vnd.tmobile-livetv - application/x-bittorrent - application/vnd.groove-tool-template - application/vnd.trid.tpt - application/vnd.trueapp - application/x-msterminal - - text/tab-separated-values - application/vnd.genomatix.tuxedo - application/vnd.mobius.txf - text/plain - application/vnd.umajin - application/vnd.unity - application/vnd.uoml+xml - text/uri-list - text/uri-list - - text/uri-list - application/x-ustar - application/vnd.uiq.theme - text/x-uuencode - application/x-cdlink - text/x-vcard - application/vnd.groove-vcard - text/x-vcalendar - application/vnd.vcx - - application/vnd.visionary - video/vnd.vivo - model/vrml - application/vnd.visio - application/vnd.vsf - application/vnd.visio - application/vnd.visio - application/vnd.visio - model/vnd.vtu - - application/voicexml+xml - audio/x-wav - audio/x-ms-wax - image/vnd.wap.wbmp - application/vnd.criticaltools.wbs+xml - application/vnd.wap.wbxml - application/vnd.ms-works - application/vnd.ms-works - application/vnd.ms-works - - video/x-ms-wm - audio/x-ms-wma - application/x-ms-wmd - application/x-msmetafile - text/vnd.wap.wml - application/vnd.wap.wmlc - text/vnd.wap.wmlscript - application/vnd.wap.wmlscriptc - video/x-ms-wmv - - video/x-ms-wmx - application/x-ms-wmz - application/vnd.wordperfect - application/vnd.ms-wpl - application/vnd.ms-works - application/vnd.wqd - application/x-mswrite - model/vrml - application/wsdl+xml - - application/wspolicy+xml - application/vnd.webturbo - video/x-ms-wvx - application/vnd.hzn-3d-crossword - application/vnd.xara - application/vnd.fujixerox.docuworks.binder - image/x-xbitmap - application/vnd.syncml.dm+xml - application/vnd.adobe.xdp+xml - - application/vnd.fujixerox.docuworks - application/xenc+xml - application/vnd.adobe.xfdf - application/vnd.xfdl - application/xhtml+xml - application/xhtml+xml - application/xv+xml - image/vnd.xiff - application/vnd.ms-excel - - application/vnd.ms-excel - application/vnd.ms-excel - application/vnd.ms-excel - application/vnd.ms-excel - application/vnd.ms-excel - application/xml - application/vnd.olpc-sugar - application/xop+xml - image/x-xpixmap - - application/vnd.is-xpr - application/vnd.ms-xpsdocument - application/xml - application/xslt+xml - application/vnd.syncml+xml - application/xspf+xml - application/vnd.mozilla.xul+xml - application/xv+xml - application/xv+xml - - image/x-xwindowdump - chemical/x-xyz - application/vnd.zzazz.deck+xml - application/zip - application/vnd.handheld-entertainment+xml - - @@ -770,25 +159,6 @@ - - - - - - - - - - Downloadable product Section - - - - - - - - - @@ -809,7 +179,6 @@ - singleton downloadable/observer prepareProductSave @@ -818,7 +187,6 @@ - singleton downloadable/observer saveDownloadableOrderItem @@ -827,7 +195,6 @@ - singleton downloadable/observer setLinkStatus @@ -868,7 +235,6 @@ - singleton downloadable/observer saveDownloadableOrderItem @@ -877,7 +243,6 @@ - singleton downloadable/observer setLinkStatus @@ -886,7 +251,6 @@ - singleton downloadable/observer setHasDownloadableProducts @@ -895,7 +259,6 @@ - singleton downloadable/observer setHasDownloadableProducts @@ -904,7 +267,6 @@ - singleton downloadable/observer isAllowedGuestCheckout diff --git a/app/code/core/Mage/Downloadable/sql/downloadable_setup/mysql4-upgrade-0.1.15-0.1.16.php b/app/code/core/Mage/Downloadable/sql/downloadable_setup/mysql4-upgrade-0.1.15-0.1.16.php new file mode 100644 index 0000000000..c30b3bcbd9 --- /dev/null +++ b/app/code/core/Mage/Downloadable/sql/downloadable_setup/mysql4-upgrade-0.1.15-0.1.16.php @@ -0,0 +1,70 @@ +startSetup(); + +$installer->addAttribute('catalog_product', 'links_exist', array( + 'type' => 'int', + 'backend' => '', + 'frontend' => '', + 'label' => '', + 'input' => '', + 'class' => '', + 'source' => '', + 'global' => true, + 'visible' => false, + 'required' => false, + 'user_defined' => false, + 'default' => '0', + 'searchable' => false, + 'filterable' => false, + 'comparable' => false, + 'visible_on_front' => false, + 'unique' => false, + 'apply_to' => 'downloadable', + 'is_configurable' => false, + 'used_in_product_listing' => 1 + )); + +$newAttributeId = $installer->getAttributeId('catalog_product', 'links_exist'); +$entityTypeId = $installer->getEntityTypeId('catalog_product'); +$newAttributeTable = $installer->getAttributeTable($entityTypeId, 'links_exist'); + +$defaultValue = 1; +$installer->run(" +INSERT INTO `{$newAttributeTable}` + (entity_id, entity_type_id, attribute_id, value) + SELECT distinct product_id, + '{$entityTypeId}' AS entity_type_id, + '{$newAttributeId}' AS attribute_id, + '{$defaultValue}' AS value + FROM `{$installer->getTable('downloadable/link')}` +"); + +$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 new file mode 100644 index 0000000000..518b338fc3 --- /dev/null +++ b/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Js.php @@ -0,0 +1,41 @@ + + */ +class Mage_Eav_Block_Adminhtml_Attribute_Edit_Js extends Mage_Adminhtml_Block_Template +{ + public function __construct() + { + parent::__construct(); + $this->setTemplate('eav/attribute/edit/js.phtml'); + } +} 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 new file mode 100644 index 0000000000..fead99b0e7 --- /dev/null +++ b/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Main/Abstract.php @@ -0,0 +1,219 @@ + + */ +abstract class Mage_Eav_Block_Adminhtml_Attribute_Edit_Main_Abstract extends Mage_Adminhtml_Block_Widget_Form +{ + protected $_attribute = null; + + public function setAttributeObject($attribute) + { + $this->_attribute = $attribute; + return $this; + } + + public function getAttributeObject() + { + if (null === $this->_attribute) { + return Mage::registry('entity_attribute'); + } + return $this->_attribute; + } + + /** + * Preparing default form elements for editing attribute + * + * @return Mage_Eav_Block_Adminhtml_Attribute_Edit_Main_Abstract + */ + protected function _prepareForm() + { + $attributeObject = $this->getAttributeObject(); + + $form = new Varien_Data_Form(array( + 'id' => 'edit_form', + 'action' => $this->getData('action'), + 'method' => 'post' + )); + + $fieldset = $form->addFieldset('base_fieldset', + array('legend'=>Mage::helper('eav')->__('Attribute Properties')) + ); + if ($attributeObject->getAttributeId()) { + $fieldset->addField('attribute_id', 'hidden', array( + 'name' => 'attribute_id', + )); + } + + $this->_addElementTypes($fieldset); + + $yesno = Mage::getModel('adminhtml/system_config_source_yesno')->toOptionArray(); + + $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', + 'required' => true, + )); + + $inputTypes = Mage::getModel('eav/adminhtml_system_config_source_inputtype')->toOptionArray(); + + $fieldset->addField('frontend_input', 'select', array( + 'name' => 'frontend_input', + 'label' => Mage::helper('eav')->__('Catalog Input Type for Store Owner'), + 'title' => Mage::helper('eav')->__('Catalog Input Type for Store Owner'), + 'value' => 'text', + 'values'=> $inputTypes + )); + + $fieldset->addField('default_value_text', 'text', array( + 'name' => 'default_value_text', + 'label' => Mage::helper('eav')->__('Default value'), + 'title' => Mage::helper('eav')->__('Default value'), + 'value' => $attributeObject->getDefaultValue(), + )); + + $fieldset->addField('default_value_yesno', 'select', array( + 'name' => 'default_value_yesno', + 'label' => Mage::helper('eav')->__('Default value'), + 'title' => Mage::helper('eav')->__('Default value'), + 'values' => $yesno, + 'value' => $attributeObject->getDefaultValue(), + )); + + $dateFormatIso = Mage::app()->getLocale()->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT); + $fieldset->addField('default_value_date', 'date', array( + 'name' => 'default_value_date', + 'label' => Mage::helper('eav')->__('Default value'), + 'title' => Mage::helper('eav')->__('Default value'), + 'image' => $this->getSkinUrl('images/grid-cal.gif'), + 'value' => $attributeObject->getDefaultValue(), + 'format' => $dateFormatIso + )); + + $fieldset->addField('default_value_textarea', 'textarea', array( + 'name' => 'default_value_textarea', + 'label' => Mage::helper('eav')->__('Default value'), + 'title' => Mage::helper('eav')->__('Default value'), + 'value' => $attributeObject->getDefaultValue(), + )); + + $fieldset->addField('is_unique', 'select', array( + 'name' => 'is_unique', + 'label' => Mage::helper('eav')->__('Unique Value'), + 'title' => Mage::helper('eav')->__('Unique Value (not shared with other products)'), + 'note' => Mage::helper('eav')->__('Not shared with other products'), + 'values' => $yesno, + )); + + $fieldset->addField('is_required', 'select', array( + 'name' => 'is_required', + 'label' => Mage::helper('eav')->__('Values Required'), + 'title' => Mage::helper('eav')->__('Values Required'), + 'values' => $yesno, + )); + + $fieldset->addField('frontend_class', 'select', array( + 'name' => 'frontend_class', + 'label' => Mage::helper('eav')->__('Input Validation for Store Owner'), + 'title' => Mage::helper('eav')->__('Input Validation for Store Owner'), + 'values'=> Mage::helper('eav')->getFrontendClasses($attributeObject->getEntityType()->getEntityTypeCode()) + )); + + if ($attributeObject->getId()) { + $form->getElement('attribute_code')->setDisabled(1); + $form->getElement('frontend_input')->setDisabled(1); + if (!$attributeObject->getIsUserDefined()) { + $form->getElement('is_unique')->setDisabled(1); + } + } + + $this->setForm($form); + + return parent::_prepareForm(); + } + + /** + * Initialize form fileds values + * + * @return Mage_Eav_Block_Adminhtml_Attribute_Edit_Main_Abstract + */ + protected function _initFormValues() + { + Mage::dispatchEvent('adminhtml_block_eav_attribute_edit_form_init', array('form' => $this->getForm())); + $this->getForm() + ->addValues($this->getAttributeObject()->getData()); + return parent::_initFormValues(); + } + + /** + * This method is called before rendering HTML + * + * @return Mage_Eav_Block_Adminhtml_Attribute_Edit_Main_Abstract + */ + protected function _beforeToHtml() + { + parent::_beforeToHtml(); + $attributeObject = $this->getAttributeObject(); + if ($attributeObject->getId()) { + $form = $this->getForm(); + $disableAttributeFields = Mage::helper('eav') + ->getAttributeLockedFields($attributeObject->getEntityType()->getEntityTypeCode()); + if (isset($disableAttributeFields[$attributeObject->getAttributeCode()])) { + foreach ($disableAttributeFields[$attributeObject->getAttributeCode()] as $field) { + if ($elm = $form->getElement($field)) { + $elm->setDisabled(1); + $elm->setReadonly(1); + } + } + } + } + return $this; + } + + /** + * Processing block html after rendering + * Adding js block to the end of this block + * + * @param string $html + * @return string + */ + protected function _afterToHtml($html) + { + $jsScripts = $this->getLayout() + ->createBlock('eav/adminhtml_attribute_edit_js')->toHtml(); + return $html.$jsScripts; + } + +} 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 new file mode 100644 index 0000000000..e862f93d5c --- /dev/null +++ b/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Options/Abstract.php @@ -0,0 +1,224 @@ + + */ +abstract class Mage_Eav_Block_Adminhtml_Attribute_Edit_Options_Abstract extends Mage_Adminhtml_Block_Widget +{ + + public function __construct() + { + parent::__construct(); + $this->setTemplate('catalog/product/attribute/options.phtml'); + } + + /** + * Preparing layout, adding buttons + * + * @return Mage_Eav_Block_Adminhtml_Attribute_Edit_Options_Abstract + */ + protected function _prepareLayout() + { + $this->setChild('delete_button', + $this->getLayout()->createBlock('adminhtml/widget_button') + ->setData(array( + 'label' => Mage::helper('eav')->__('Delete'), + 'class' => 'delete delete-option' + ))); + + $this->setChild('add_button', + $this->getLayout()->createBlock('adminhtml/widget_button') + ->setData(array( + 'label' => Mage::helper('eav')->__('Add Option'), + 'class' => 'add', + 'id' => 'add_new_option_button' + ))); + return parent::_prepareLayout(); + } + + /** + * Retrieve HTML of delete button + * + * @return string + */ + public function getDeleteButtonHtml() + { + return $this->getChildHtml('delete_button'); + } + + /** + * Retrieve HTML of add button + * + * @return string + */ + public function getAddNewButtonHtml() + { + return $this->getChildHtml('add_button'); + } + + /** + * Retrieve stores collection with default store + * + * @return Mage_Core_Model_Mysql4_Store_Collection + */ + public function getStores() + { + $stores = $this->getData('stores'); + if (is_null($stores)) { + $stores = Mage::getModel('core/store') + ->getResourceCollection() + ->setLoadDefault(true) + ->load(); + $this->setData('stores', $stores); + } + return $stores; + } + + /** + * Retrieve attribute option values if attribute input type select or multiselect + * + * @return array + */ + public function getOptionValues() + { + $attributeType = $this->getAttributeObject()->getFrontendInput(); + $defaultValues = $this->getAttributeObject()->getDefaultValue(); + if ($attributeType == 'select' || $attributeType == 'multiselect') { + $defaultValues = explode(',', $defaultValues); + } else { + $defaultValues = array(); + } + + switch ($attributeType) { + case 'select': + $inputType = 'radio'; + break; + case 'multiselect': + $inputType = 'checkbox'; + break; + default: + $inputType = ''; + break; + } + + $values = $this->getData('option_values'); + if (is_null($values)) { + $values = array(); + $optionCollection = Mage::getResourceModel('eav/entity_attribute_option_collection') + ->setAttributeFilter($this->getAttributeObject()->getId()) + ->setPositionOrder('desc', true) + ->load(); + + foreach ($optionCollection as $option) { + $value = array(); + if (in_array($option->getId(), $defaultValues)) { + $value['checked'] = 'checked="checked"'; + } else { + $value['checked'] = ''; + } + + $value['intype'] = $inputType; + $value['id'] = $option->getId(); + $value['sort_order'] = $option->getSortOrder(); + foreach ($this->getStores() as $store) { + $storeValues = $this->getStoreOptionValues($store->getId()); + if (isset($storeValues[$option->getId()])) { + $value['store'.$store->getId()] = htmlspecialchars($storeValues[$option->getId()]); + } + else { + $value['store'.$store->getId()] = ''; + } + } + $values[] = new Varien_Object($value); + } + $this->setData('option_values', $values); + } + + return $values; + } + + /** + * Retrieve frontend labels of attribute for each store + * + * @return array + */ + public function getLabelValues() + { + $values = array(); + $values[0] = $this->getAttributeObject()->getFrontend()->getLabel(); + // it can be array and cause bug + $frontendLabel = $this->getAttributeObject()->getFrontend()->getLabel(); + if (is_array($frontendLabel)) { + $frontendLabel = array_shift($frontendLabel); + } + $storeLabels = $this->getAttributeObject()->getStoreLabels(); + foreach ($this->getStores() as $store) { + if ($store->getId() != 0) { + $values[$store->getId()] = isset($storeLabels[$store->getId()]) ? $storeLabels[$store->getId()] : ''; + } + } + return $values; + } + + /** + * Retrieve attribute option values for given store id + * + * @param integer $storeId + * @return array + */ + public function getStoreOptionValues($storeId) + { + $values = $this->getData('store_option_values_'.$storeId); + if (is_null($values)) { + $values = array(); + $valuesCollection = Mage::getResourceModel('eav/entity_attribute_option_collection') + ->setAttributeFilter($this->getAttributeObject()->getId()) + ->setStoreFilter($storeId, false) + ->load(); + foreach ($valuesCollection as $item) { + $values[$item->getId()] = $item->getValue(); + } + $this->setData('store_option_values_'.$storeId, $values); + } + return $values; + } + + /** + * Retrieve attribute object from registry + * + * @return Mage_Eav_Model_Entity_Attribute_Abstract + */ + public function getAttributeObject() + { + return Mage::registry('entity_attribute'); + } + +} 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 new file mode 100644 index 0000000000..e0bbed5c11 --- /dev/null +++ b/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Grid/Abstract.php @@ -0,0 +1,103 @@ + + */ +abstract class Mage_Eav_Block_Adminhtml_Attribute_Grid_Abstract extends Mage_Adminhtml_Block_Widget_Grid +{ + + public function __construct() + { + parent::__construct(); + $this->setId('attributeGrid'); + $this->setDefaultSort('attribute_code'); + $this->setDefaultDir('ASC'); + } + + /** + * Prepare default grid column + * + * @return Mage_Eav_Block_Adminhtml_Attribute_Grid_Abstract + */ + protected function _prepareColumns() + { + parent::_prepareColumns(); + + $this->addColumn('attribute_code', array( + 'header'=>Mage::helper('eav')->__('Attribute Code'), + 'sortable'=>true, + 'index'=>'attribute_code' + )); + + $this->addColumn('frontend_label', array( + 'header'=>Mage::helper('eav')->__('Attribute Label'), + 'sortable'=>true, + 'index'=>'frontend_label' + )); + + $this->addColumn('is_required', array( + 'header'=>Mage::helper('eav')->__('Required'), + 'sortable'=>true, + 'index'=>'is_required', + 'type' => 'options', + 'options' => array( + '1' => Mage::helper('eav')->__('Yes'), + '0' => Mage::helper('eav')->__('No'), + ), + 'align' => 'center', + )); + + $this->addColumn('is_user_defined', array( + 'header'=>Mage::helper('eav')->__('System'), + 'sortable'=>true, + 'index'=>'is_user_defined', + 'type' => 'options', + 'align' => 'center', + 'options' => array( + '0' => Mage::helper('eav')->__('Yes'), // intended reverted use + '1' => Mage::helper('eav')->__('No'), // intended reverted use + ), + )); + + return $this; + } + + /** + * Return url of given row + * + * @return string + */ + public function getRowUrl($row) + { + return $this->getUrl('*/*/edit', array('attribute_id' => $row->getAttributeId())); + } + +} diff --git a/app/code/core/Mage/Eav/Helper/Data.php b/app/code/core/Mage/Eav/Helper/Data.php index 2884d86245..7d810148b9 100644 --- a/app/code/core/Mage/Eav/Helper/Data.php +++ b/app/code/core/Mage/Eav/Helper/Data.php @@ -29,5 +29,103 @@ */ class Mage_Eav_Helper_Data extends Mage_Core_Helper_Abstract { + protected $_attributesLockedFields = array(); + protected $_entityTypeFrontendClasses = array(); + + /** + * Return default frontend classes value labal array + * + * @return array + */ + protected function _getDefaultFrontendClasses() + { + return array( + array( + 'value' => '', + 'label' => Mage::helper('eav')->__('None') + ), + array( + 'value' => 'validate-number', + 'label' => Mage::helper('eav')->__('Decimal Number') + ), + array( + 'value' => 'validate-digits', + 'label' => Mage::helper('eav')->__('Integer Number') + ), + array( + 'value' => 'validate-email', + 'label' => Mage::helper('eav')->__('Email') + ), + array( + 'value' => 'validate-url', + 'label' => Mage::helper('eav')->__('Url') + ), + array( + 'value' => 'validate-alpha', + 'label' => Mage::helper('eav')->__('Letters') + ), + array( + 'value' => 'validate-alphanum', + 'label' => Mage::helper('eav')->__('Letters(a-zA-Z) or Numbers(0-9)') + ) + ); + } + + /** + * Return merged default and entity type frontend classes value label array + * + * @param string $entityTypeCode + * @return array + */ + public function getFrontendClasses($entityTypeCode) + { + $_defaultClasses = $this->_getDefaultFrontendClasses(); + if (isset($this->_entityTypeFrontendClasses[$entityTypeCode])) { + return array_merge( + $_defaultClasses, + $this->_entityTypeFrontendClasses[$entityTypeCode] + ); + } + $_entityTypeClasses = Mage::app()->getConfig() + ->getNode('global/eav_frontendclasses/' . $entityTypeCode); + if ($_entityTypeClasses) { + foreach ($_entityTypeClasses->children() as $item) { + $this->_entityTypeFrontendClasses[$entityTypeCode][] = array( + 'value' => (string)$item->value, + 'label' => (string)$item->label + ); + } + return array_merge( + $_defaultClasses, + $this->_entityTypeFrontendClasses[$entityTypeCode] + ); + } + return $_defaultClasses; + } + + /** + * Retrieve attributes locked fields to edit + * + * @param string $entityTypeCode + * @return array + */ + public function getAttributeLockedFields($entityTypeCode) + { + if (!$entityTypeCode) { + return array(); + } + 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()); + } + return $this->_attributesLockedFields[$entityTypeCode]; + } + return array(); + } } 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 new file mode 100644 index 0000000000..6c50db401e --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Adminhtml/System/Config/Source/Inputtype.php @@ -0,0 +1,41 @@ + 'text', 'label' => Mage::helper('eav')->__('Text Field')), + array('value' => 'textarea', 'label' => Mage::helper('eav')->__('Text Area')), + array('value' => 'date', 'label' => Mage::helper('eav')->__('Date')), + array('value' => 'boolean', 'label' => Mage::helper('eav')->__('Yes/No')), + array('value' => 'multiselect', 'label' => Mage::helper('eav')->__('Multiple Select')), + array('value' => 'select', 'label' => Mage::helper('eav')->__('Dropdown')) + ); + } + +} diff --git a/app/code/core/Mage/Eav/Model/Config.php b/app/code/core/Mage/Eav/Model/Config.php index 72a3a3f877..29bd286177 100644 --- a/app/code/core/Mage/Eav/Model/Config.php +++ b/app/code/core/Mage/Eav/Model/Config.php @@ -454,6 +454,10 @@ public function getEntityAttributeCodes($entityType, $object=null) if (($object instanceof Varien_Object) && $object->getAttributeSetId()) { $attributeSetId = $object->getAttributeSetId(); } + $storeId = 0; + if (($object instanceof Varien_Object) && $object->getStoreId()) { + $storeId = $object->getStoreId(); + } $cacheKey = sprintf('%d-%d', $entityType->getId(), $attributeSetId); if (isset($this->_attributeCodes[$cacheKey])) { return $this->_attributeCodes[$cacheKey]; @@ -465,6 +469,7 @@ public function getEntityAttributeCodes($entityType, $object=null) ->setEntityTypeFilter($entityType->getId()) ->setAttributeSetFilter($attributeSetId) // ->addSetInfo() + ->addStoreLabel($storeId) ->getData(); $attributes = array(); foreach ($attributesInfo as $attributeData) { @@ -601,8 +606,8 @@ public function loadCollectionAttributes($entityType, $attributes) if (empty($attributes)) { return $this; } - - $attributesInfo = Mage::getResourceModel('eav/entity_attribute_collection') + $attributeCollection = $entityType->getEntityAttributeCollection(); + $attributesInfo = Mage::getResourceModel($attributeCollection) ->useLoadDataFields() ->setEntityTypeFilter($entityType->getId()) ->setCodeFilter($attributes) 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 82c0614f5b..f8a9c96126 100644 --- a/app/code/core/Mage/Eav/Model/Convert/Adapter/Entity.php +++ b/app/code/core/Mage/Eav/Model/Convert/Adapter/Entity.php @@ -164,8 +164,8 @@ public function setFilter($attrFilterArray, $attrToDb = null, $bind = null, $joi case 'datetimeFromTo': $attr = array( 'attribute' => $keyDB, - 'from' => $val['from'], - 'to' => $val['to'], + 'from' => isset($val['from']) ? $val['from'] : null, + 'to' => isset($val['to']) ? $val['to'] : null, 'datetime' => true ); break; diff --git a/app/code/core/Mage/Eav/Model/Entity/Abstract.php b/app/code/core/Mage/Eav/Model/Entity/Abstract.php index 90d0c14532..366358a26c 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Abstract.php @@ -320,7 +320,7 @@ public function unsetAttributes($attributes=null) * If attribute is not found false is returned * * @param string|integer|Mage_Core_Model_Config_Element $attribute - * @return Mage_Eav_Model_Entity_Attribute_Abstract + * @return Mage_Eav_Model_Entity_Attribute_Abstract || false */ public function getAttribute($attribute) { @@ -928,6 +928,12 @@ public function save(Varien_Object $object) return $this; } + /** + * Retrieve Object instance with original data + * + * @param Varien_Object $object + * @return Varien_Object + */ protected function _getOrigObject($object) { $className = get_class($object); @@ -950,37 +956,53 @@ protected function _getOrigObject($object) */ protected function _collectSaveData($newObject) { - $newData = $newObject->getData(); - $entityId = $newObject->getData($this->getEntityIdField()); + $newData = $newObject->getData(); + $entityId = $newObject->getData($this->getEntityIdField()); + + // define result data + $entityRow = array(); + $insert = array(); + $update = array(); + $delete = array(); + if (!empty($entityId)) { + $origData = $newObject->getOrigData(); /** - * get current data in db for this entity + * get current data in db for this entity if original data is empty */ - /*$className = get_class($newObject); - $origObject = new $className(); - $origObject->setData(array()); - $this->load($origObject, $entityId); - $origData = $origObject->getOrigData();*/ - $origData = $this->_getOrigObject($newObject)->getOrigData(); + if (empty($origData)) { + $origData = $this->_getOrigObject($newObject)->getOrigData(); + } /** * drop attributes that are unknown in new data * not needed after introduction of partial entity loading */ - foreach ($origData as $k=>$v) { + foreach ($origData as $k => $v) { if (!array_key_exists($k, $newData)) { unset($origData[$k]); } } + } else { + $origData = array(); } - foreach ($newData as $k=>$v) { + $staticFields = $this->_getWriteAdapter()->describeTable($this->getEntityTable()); + $staticFields = array_keys($staticFields); + $attributeCodes = array_keys($this->_attributesByCode); + + foreach ($newData as $k => $v) { /** * Check attribute information */ if (is_numeric($k) || is_array($v)) { continue; - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid data object key')); + } + /** + * Check if data key is presented in static fields or attribute codes + */ + if (!in_array($k, $staticFields) && !in_array($k, $attributeCodes)) { + continue; } $attribute = $this->getAttribute($k); @@ -1001,21 +1023,19 @@ protected function _collectSaveData($newObject) /** * Check comparability for attribute value */ - if (isset($origData[$k])) { - if ($attribute->isValueEmpty($v)) { + if (array_key_exists($k, $origData)) { + if ($this->_isAttributeValueEmpty($attribute, $v)) { $delete[$attribute->getBackend()->getTable()][] = array( 'attribute_id' => $attrId, 'value_id' => $attribute->getBackend()->getValueId() ); - } - elseif ($v!==$origData[$k]) { + } else if ($v !== $origData[$k]) { $update[$attrId] = array( 'value_id' => $attribute->getBackend()->getValueId(), 'value' => $v, ); } - } - elseif (!$attribute->isValueEmpty($v)) { + } else if (!$this->_isAttributeValueEmpty($attribute, $v)) { $insert[$attrId] = $v; } } @@ -1103,7 +1123,7 @@ protected function _processSaveData($saveData) * insert attribute values */ if (!empty($insert)) { - foreach ($insert as $attrId=>$value) { + foreach ($insert as $attrId => $value) { $attribute = $this->getAttribute($attrId); $this->_insertAttribute($newObject, $attribute, $value); } @@ -1113,7 +1133,7 @@ protected function _processSaveData($saveData) * update attribute values */ if (!empty($update)) { - foreach ($update as $attrId=>$v) { + foreach ($update as $attrId => $v) { $attribute = $this->getAttribute($attrId); $this->_updateAttribute($newObject, $attribute, $v['value_id'], $v['value']); } @@ -1123,7 +1143,7 @@ protected function _processSaveData($saveData) * delete empty attribute values */ if (!empty($delete)) { - foreach ($delete as $table=>$values) { + foreach ($delete as $table => $values) { $this->_deleteAttributes($newObject, $table, $values); } } @@ -1399,4 +1419,16 @@ protected function _afterSetConfig() // Varien_Profiler::stop(__METHOD__); // return $this; } + + /** + * Check is attribute value empty + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @param mixed $value + * @return bool + */ + protected function _isAttributeValueEmpty(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $value) + { + return $attribute->isValueEmpty($value); + } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute.php b/app/code/core/Mage/Eav/Model/Entity/Attribute.php index 16768b743f..da6bc61177 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute.php @@ -101,17 +101,6 @@ protected function _beforeSave() Mage::throwException(Mage::helper('eav')->__('The attribute code \'%s\' is reserved by system. Please, try another attribute code.', $this->_data['attribute_code'])); } - // prevent changing attribute scope, if used in configurable products - if (isset($this->_origData['is_global'])) { - if (!isset($this->_data['is_global'])) { - Mage::throwException('0_o'); - } - if (($this->_data['is_global'] != $this->_origData['is_global']) - && $this->_getResource()->isUsedBySuperProducts($this)) { - Mage::throwException(Mage::helper('eav')->__('Scope must not be changed, because the attribute is used in configurable products.')); - } - } - if ($this->getBackendType() == 'datetime') { if (!$this->getBackendModel()) { $this->setBackendModel('eav/entity_attribute_backend_datetime'); @@ -139,12 +128,6 @@ protected function _beforeSave() } } - if ($this->getFrontendInput() == 'price') { - if (!$this->getBackendModel()) { - $this->setBackendModel('catalog/product_attribute_backend_price'); - } - } - return parent::_beforeSave(); } @@ -252,4 +235,27 @@ public function getAttributeCodesByFrontendType($type) { return $this->getResource()->getAttributeCodesByFrontendType($type); } + + /** + * Return array of labels of stores + * + * @return array + */ + public function getStoreLabels() + { + if (!$this->getData('store_labels')) { + $this->setData('store_labels', $this->getResource()->getStoreLabelsByAttributeId($this->getId())); + } + return $this->getData('store_labels'); + } + + /** + * Return store label of attribute + * + * @return string + */ + public function getStoreLabel() + { + return $this->getData('store_label'); + } } 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 70f1933893..f429f4ef2d 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php @@ -246,6 +246,12 @@ public function setEntityType($type) return $this; } + /** + * Return is attribute global + * + * @deprecated moved to catalog attribute model + * @return integer + */ public function getIsGlobal() { return $this->_getData('is_global'); 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 ce1eaef02d..c3e54141a0 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 @@ -28,15 +28,16 @@ class Mage_Eav_Model_Entity_Attribute_Backend_Datetime extends Mage_Eav_Model_En { public function beforeSave($object) { - $_formated = $object->getData($this->getAttribute()->getName() . '_is_formated'); - if (!$_formated) { + $attributeName = $this->getAttribute()->getName(); + $_formated = $object->getData($attributeName . '_is_formated'); + if (!$_formated && $object->hasData($attributeName)) { try { - $value = $this->formatDate($object->getData($this->getAttribute()->getName())); + $value = $this->formatDate($object->getData($attributeName)); } catch (Exception $e) { throw new Exception("Invalid date."); } - $object->setData($this->getAttribute()->getName(), $value); - $object->setData($this->getAttribute()->getName() . '_is_formated', true); + $object->setData($attributeName, $value); + $object->setData($attributeName . '_is_formated', true); } } 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 a7f7f19c1d..ab7a60ec3e 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 @@ -127,7 +127,7 @@ public function addValueSortToCollection($collection, $dir = 'asc') . " AND `{$valueTable2}`.`store_id`='{$collection->getStoreId()}'", array() ); - $valueExpr = new Zend_Db_Expr("IFNULL(`{$valueTable2}`.`value`, `{$valueTable1}`.`value`)"); + $valueExpr = new Zend_Db_Expr("IF(`{$valueTable2}`.`value_id`>0, `{$valueTable2}`.`value`, `{$valueTable1}`.`value`)"); Mage::getResourceModel('eav/entity_attribute_option') ->addOptionValueToCollection($collection, $this->getAttribute(), $valueExpr); 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 d07c367406..3ad7d268d3 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Collection/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Collection/Abstract.php @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Entity/Attribute/Model - collection abstract * @@ -811,11 +810,11 @@ public function load($printQuery = false, $logQuery = false) } /** - * Retrive all ids for collection + * Clone and reset collection * - * @return array + * @return Mage_Eav_Model_Entity_Collection_Abstract */ - public function getAllIds($limit=null, $offset=null) + protected function _getAllIdsSelect($limit=null, $offset=null) { $idsSelect = clone $this->getSelect(); $idsSelect->reset(Zend_Db_Select::ORDER); @@ -824,7 +823,17 @@ public function getAllIds($limit=null, $offset=null) $idsSelect->reset(Zend_Db_Select::COLUMNS); $idsSelect->from(null, 'e.'.$this->getEntity()->getIdFieldName()); $idsSelect->limit($limit, $offset); - return $this->getConnection()->fetchCol($idsSelect, $this->_bindParams); + return $idsSelect; + } + + /** + * Retrive all ids for collection + * + * @return array + */ + public function getAllIds($limit=null, $offset=null) + { + return $this->getConnection()->fetchCol($this->_getAllIdsSelect($limit, $offset), $this->_bindParams); } /** diff --git a/app/code/core/Mage/Eav/Model/Entity/Setup.php b/app/code/core/Mage/Eav/Model/Entity/Setup.php index 26217ff49b..206cec2313 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Setup.php +++ b/app/code/core/Mage/Eav/Model/Entity/Setup.php @@ -92,12 +92,14 @@ 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, + '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'] : '', ); if ($this->getEntityType($code, 'entity_type_id')) { @@ -518,55 +520,54 @@ protected function _getValue($array, $key, $default = null) } /** - * Add attribute to an entity type - * - * If attribute is system will add to all existing attribute sets + * Prepare attribute values to save * - * @param string|integer $entityTypeId - * @param string $code * @param array $attr - * @return Mage_Eav_Model_Entity_Setup + * @return array */ - public function addAttribute($entityTypeId, $code, array $attr) + protected function _prepareValues($attr) { - $entityTypeId = $this->getEntityTypeId($entityTypeId); + $data = array(); $data = array( - 'entity_type_id' => $entityTypeId, - 'attribute_code' => $code, '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_input_renderer' => $this->_getValue($attr, 'input_renderer', ''), 'frontend_label' => $this->_getValue($attr, 'label', ''), 'frontend_class' => $this->_getValue($attr, 'frontend_class', ''), 'source_model' => $this->_getValue($attr, 'source', ''), - 'is_global' => $this->_getValue($attr, 'global', 1), - 'is_visible' => $this->_getValue($attr, 'visible', 1), 'is_required' => $this->_getValue($attr, 'required', 1), 'is_user_defined' => $this->_getValue($attr, 'user_defined', 0), 'default_value' => $this->_getValue($attr, 'default', ''), - 'is_searchable' => $this->_getValue($attr, 'searchable', 0), - 'is_filterable' => $this->_getValue($attr, 'filterable', 0), - 'is_comparable' => $this->_getValue($attr, 'comparable', 0), - 'is_visible_on_front' => $this->_getValue($attr, 'visible_on_front', 0), - 'is_html_allowed_on_front' => $this->_getValue($attr, 'is_html_allowed_on_front', 0), - 'is_visible_in_advanced_search' - => $this->_getValue($attr, 'visible_in_advanced_search', 0), - 'is_used_for_price_rules' => $this->_getValue($attr, 'used_for_price_rules', 1), - 'is_filterable_in_search' => $this->_getValue($attr, 'filterable_in_search', 0), - 'used_in_product_listing' => $this->_getValue($attr, 'used_in_product_listing', 0), - 'used_for_sort_by' => $this->_getValue($attr, 'used_for_sort_by', 0), 'is_unique' => $this->_getValue($attr, 'unique', 0), - 'apply_to' => $this->_getValue($attr, 'apply_to', ''), - 'is_configurable' => $this->_getValue($attr, 'is_configurable', 1), 'note' => $this->_getValue($attr, 'note', ''), - 'position' => $this->_getValue($attr, 'position', 0), ); + return $data; + } - $sortOrder = isset($attr['sort_order']) ? $attr['sort_order'] : null; + /** + * Add attribute to an entity type + * + * If attribute is system will add to all existing attribute sets + * + * @param string|integer $entityTypeId + * @param string $code + * @param array $attr + * @return Mage_Eav_Model_Entity_Setup + */ + public function addAttribute($entityTypeId, $code, array $attr) + { + $entityTypeId = $this->getEntityTypeId($entityTypeId); + $data = array_merge( + array( + 'entity_type_id' => $entityTypeId, + 'attribute_code' => $code + ), + $this->_prepareValues($attr) + ); + $sortOrder = isset($attr['sort_order']) ? $attr['sort_order'] : null; if ($id = $this->getAttribute($entityTypeId, $code, 'attribute_id')) { $this->updateAttribute($entityTypeId, $id, $data, null, $sortOrder); } else { @@ -580,7 +581,7 @@ public function addAttribute($entityTypeId, $code, array $attr) $this->addAttributeToSet($entityTypeId, $set['attribute_set_id'], $attr['group'], $code, $sortOrder); } } - if (empty($attr['is_user_defined'])) { + if (empty($attr['user_defined'])) { $sets = $this->_conn->fetchAll('select * from '.$this->getTable('eav/attribute_set').' where entity_type_id=?', $entityTypeId); foreach ($sets as $set) { $this->addAttributeToSet($entityTypeId, $set['attribute_set_id'], $this->_generalGroupName, $code, $sortOrder); @@ -612,7 +613,7 @@ public function addAttributeOption($option) if (!empty($option['delete'][$optionId])) { if ($intOptionId) { $condition = $this->_conn->quoteInto('option_id=?', $intOptionId); - $write->delete($optionTable, $condition); + $this->_conn->delete($optionTable, $condition); } continue; } @@ -650,7 +651,7 @@ public function addAttributeOption($option) } /** - * Update Attribute data + * Update Attribute data and Attribute additional data * * @param mixed $entityTypeId * @param mixed $id @@ -660,6 +661,23 @@ public function addAttributeOption($option) * @return Mage_Eav_Model_Entity_Setup */ public function updateAttribute($entityTypeId, $id, $field, $value=null, $sortOrder=null) + { + $this->_updateAttribute($entityTypeId, $id, $field, $value, $sortOrder); + $this->_updateAttributeAdditionalData($entityTypeId, $id, $field, $value); + return $this; + } + + /** + * Update Attribute data + * + * @param mixed $entityTypeId + * @param mixed $id + * @param string $field + * @param mixed $value + * @param int $sortOrder + * @return Mage_Eav_Model_Entity_Setup + */ + protected function _updateAttribute($entityTypeId, $id, $field, $value=null, $sortOrder=null) { if (!is_null($sortOrder)) { $this->updateTableRow('eav/entity_attribute', @@ -695,6 +713,49 @@ public function updateAttribute($entityTypeId, $id, $field, $value=null, $sortOr return $this; } + /** + * Update Attribute Additional data + * + * @param mixed $entityTypeId + * @param mixed $id + * @param string $field + * @param mixed $value + * @return Mage_Eav_Model_Entity_Setup + */ + 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)); + 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; + } + } + if (!$bind) { + return $this; + } + $field = $bind; + } + else { + if (!isset($attributeFields[$field])) { + return $this; + } + } + $this->updateTableRow($this->getTable($additionalTable), + 'attribute_id', $this->getAttributeId($entityTypeId, $id), + $field, $value + ); + } + return $this; + } + /** * Retrieve Attribute Data By Id or Code * @@ -705,11 +766,40 @@ public function updateAttribute($entityTypeId, $id, $field, $value=null, $sortOr */ public function getAttribute($entityTypeId, $id, $field=null) { - return $this->getTableRow('eav/attribute', - is_numeric($id) ? 'attribute_id' : 'attribute_code', $id, - $field, - 'entity_type_id', $this->getEntityTypeId($entityTypeId) - ); + $joinTable = $this->getEntityType($entityTypeId, 'additional_attribute_table'); + if (!$joinTable) { + return $this->getTableRow('eav/attribute', + is_numeric($id) ? 'attribute_id' : 'attribute_code', $id, + $field, + 'entity_type_id', $this->getEntityTypeId($entityTypeId) + ); + } + $mainTable = $this->getTable('eav/attribute'); + $joinTable = $this->getTable($joinTable); + $parentId = $this->getEntityTypeId($entityTypeId); + $idField = (is_numeric($id) ? 'attribute_id' : 'attribute_code'); + $table = $mainTable . '-' . $joinTable; + if (empty($this->_setupCache[$table][$parentId][$id])) { + $sql = "select + $mainTable.*, + $joinTable.* + from + $mainTable + inner join $joinTable on $joinTable.attribute_id=$mainTable.attribute_id + where + $mainTable.$idField = :idField + and $mainTable.entity_type_id = :parentId"; + $bind = array( + 'idField' => $id, + 'parentId' => $parentId + ); + $this->_setupCache[$mainTable][$parentId][$id] = $this->_conn->fetchRow($sql, $bind); + $this->_conn->fetchAll($sql, $bind); + } + if (is_null($field)) { + return $this->_setupCache[$mainTable][$parentId][$id]; + } + return isset($this->_setupCache[$mainTable][$parentId][$id][$field]) ? $this->_setupCache[$mainTable][$parentId][$id][$field] : false; } /** @@ -775,8 +865,17 @@ public function getAttributeTable($entityTypeId, $id) public function removeAttribute($entityTypeId, $code) { $attributeId = $this->getAttributeId($entityTypeId, $code); + $additionalTable = $this->getEntityType($entityTypeId, 'additional_attribute_table'); if ($attributeId) { $this->deleteTableRow('eav/attribute', 'attribute_id', $attributeId); + if ($additionalTable) { + $additionalTable = $this->getTable($additionalTable); + $condition = $this->_conn->quoteInto("attribute_id=?", $attributeId); + $this->_conn->delete($additionalTable, $condition); + if (isset($this->_setupCache[$table.'-'.$additionalTable][0][$attributeId])) { + unset($this->_setupCache[$table][0][$attributeId]); + } + } } return $this; } @@ -1054,6 +1153,7 @@ protected function _getAttributeTableFields() { */ protected function _insertAttribute(array $data) { $bind = array(); + $fields = $this->_getAttributeTableFields(); foreach ($data as $k => $v) { @@ -1066,7 +1166,40 @@ protected function _insertAttribute(array $data) { } $this->getConnection()->insert($this->getTable('eav/attribute'), $bind); + $attributeId = $this->getConnection()->lastInsertId(); + $this->_insertAttributeAdditionalData( + $data['entity_type_id'], + array_merge(array('attribute_id' => $attributeId), $data) + ); + return $this; + } + /** + * Insert attribute additional data + * + * @param array $data + * @return Mage_Eav_Model_Entity_Setup + */ + protected function _insertAttributeAdditionalData($entityTypeId, array $data) + { + $additionalTable = $this->getEntityType($entityTypeId, 'additional_attribute_table'); + if (!$additionalTable) { + return $this; + } + $additionalTableExists = $this->getConnection()->showTableStatus($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; + } + } + if (!$bind) { + return $this; + } + $this->getConnection()->insert($this->getTable($additionalTable), $bind); + } return $this; } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Eav/Model/Entity/Type.php b/app/code/core/Mage/Eav/Model/Entity/Type.php index 37f4439837..7d085c82b7 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Type.php +++ b/app/code/core/Mage/Eav/Model/Entity/Type.php @@ -273,4 +273,17 @@ public function getEntity() { return Mage::getResourceSingleton($this->_data['entity_model']); } + + /** + * Return attribute collection. If not specify return default + * + * @return string + */ + public function getEntityAttributeCollection() + { + if ($collection = $this->_getData('entity_attribute_collection')) { + return $collection; + } + return 'eav/entity_attribute_collection'; + } } diff --git a/app/code/core/Mage/Eav/Model/Form/Element.php b/app/code/core/Mage/Eav/Model/Form/Element.php new file mode 100644 index 0000000000..99d582db9f --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Form/Element.php @@ -0,0 +1,105 @@ + + */ +class Mage_Eav_Model_Form_Element extends Mage_Core_Model_Abstract +{ + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'eav_form_element'; + + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('eav/form_element'); + } + + /** + * Retrieve resource instance wrapper + * + * @return Mage_Eav_Model_Mysql4_Form_Element + */ + protected function _getResource() + { + return parent::_getResource(); + } + + /** + * Retrieve resource collection instance wrapper + * + * @return Mage_Eav_Model_Mysql4_Form_Element_Collection + */ + public function getCollection() + { + return parent::getCollection(); + } + + /** + * Validate data before save data + * + * @throws Mage_Core_Exception + * @return Mage_Eav_Model_Form_Element + */ + protected function _beforeSave() + { + if (!$this->getTypeId()) { + Mage::throwException(Mage::helper('eav')->__('Invalid form type')); + } + if (!$this->getAttributeId()) { + Mage::throwException(Mage::helper('eav')->__('Invalid EAV attribute')); + } + + return parent::_beforeSave(); + } + + /** + * Retrieve EAV Attribute instance + * + * @return Mage_Eav_Model_Entity_Attribute + */ + public function getAttribute() + { + if (!$this->hasData('attribute')) { + $attribute = Mage::getSingleton('eav/config') + ->getAttribute($this->getEntityTypeId(), $this->getAttributeId()); + $this->setData('attribute', $attribute); + } + return $this->_getData('attribute'); + } +} diff --git a/app/code/core/Mage/Eav/Model/Form/Fieldset.php b/app/code/core/Mage/Eav/Model/Form/Fieldset.php new file mode 100644 index 0000000000..f5b8b8eb58 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Form/Fieldset.php @@ -0,0 +1,143 @@ + + */ +class Mage_Eav_Model_Form_Fieldset extends Mage_Core_Model_Abstract +{ + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'eav_form_fieldset'; + + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('eav/form_fieldset'); + } + + /** + * Retrieve resource instance wrapper + * + * @return Mage_Eav_Model_Mysql4_Form_Fieldset + */ + protected function _getResource() + { + return parent::_getResource(); + } + + /** + * Retrieve resource collection instance wrapper + * + * @return Mage_Eav_Model_Mysql4_Form_Fieldset_Collection + */ + public function getCollection() + { + return parent::getCollection(); + } + + /** + * Validate data before save data + * + * @throws Mage_Core_Exception + * @return Mage_Eav_Model_Form_Fieldset + */ + protected function _beforeSave() + { + if (!$this->getTypeId()) { + Mage::throwException(Mage::helper('eav')->__('Invalid form type')); + } + if (!$this->getStoreId() && $this->getLabel()) { + $this->setStoreLabel($this->getStoreId(), $this->getLabel()); + } + + return parent::_beforeSave(); + } + + /** + * Retrieve fieldset labels for stores + * + * @return array + */ + public function getLabels() + { + if (!$this->hasData('labels')) { + $this->setData('labels', $this->_getResource()->getLabels($this)); + } + return $this->_getData('labels'); + } + + /** + * Set fieldset store labels + * Input array where key - store_id and value = label + * + * @param array $labels + * @return Mage_Eav_Model_Form_Fieldset + */ + public function setLabels(array $labels) + { + return $this->setData('labels', $labels); + } + + /** + * Set fieldset store label + * + * @param int $storeId + * @param string $label + * @return Mage_Eav_Model_Form_Fieldset + */ + public function setStoreLabel($storeId, $label) + { + $labels = $this->getLabels(); + $labels[$storeId] = $label; + + return $this->setLabels($labels); + } + + /** + * Retrieve label store scope + * + * @return int + */ + public function getStoreId() + { + if (!$this->hasStoreId()) { + $this->setData('store_id', Mage::app()->getStore()->getId()); + } + return $this->_getData('store_id'); + } +} diff --git a/app/code/core/Mage/Eav/Model/Form/Type.php b/app/code/core/Mage/Eav/Model/Form/Type.php new file mode 100644 index 0000000000..f65acb4a24 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Form/Type.php @@ -0,0 +1,158 @@ + + */ +class Mage_Eav_Model_Form_Type extends Mage_Core_Model_Abstract +{ + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'eav_form_type'; + + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('eav/form_type'); + } + + /** + * Retrieve resource instance wrapper + * + * @return Mage_Eav_Model_Mysql4_Form_Type + */ + protected function _getResource() + { + return parent::_getResource(); + } + + /** + * Retrieve resource collection instance wrapper + * + * @return Mage_Eav_Model_Mysql4_Form_Type_Collection + */ + public function getCollection() + { + return parent::getCollection(); + } + + /** + * Retrieve assigned Eav Entity types + * + * @return array + */ + public function getEntityTypes() + { + if (!$this->hasData('entity_types')) { + $this->setData('entity_types', $this->_getResource()->getEntityTypes($this)); + } + return $this->_getData('entity_types'); + } + + /** + * Set assigned Eav Entity types + * + * @param array $entityTypes + * @return Mage_Eav_Model_Form_Type + */ + public function setEntityTypes(array $entityTypes) + { + $this->setData('entity_types', $entityTypes); + return $this; + } + + /** + * Assign Entity Type to Form Type + * + * @param int $entityTypeId + * @return Mage_Eav_Model_Form_Type + */ + public function addEntityType($entityTypeId) + { + $entityTypes = $this->getEntityTypes(); + if (!empty($entityTypeId) && !in_array($entityTypeId, $entityTypes)) { + $entityTypes[] = $entityTypeId; + $this->setEntityTypes($entityTypes); + } + return $this; + } + + /** + * Copy Form Type properties from skeleton form type + * + * @param Mage_Eav_Model_Form_Type $skeleton + * @return Mage_Eav_Model_Form_Type + */ + public function createFromSkeleton(Mage_Eav_Model_Form_Type $skeleton) + { + $fieldsetCollection = Mage::getModel('eav/form_fieldset')->getCollection() + ->addTypeFilter($skeleton) + ->setSortOrder(); + $elementCollection = Mage::getModel('eav/form_element')->getCollection() + ->addTypeFilter($skeleton) + ->setSortOrder(); + + // copy fieldsets + $fieldsetMap = array(); + foreach ($fieldsetCollection as $skeletonFieldset) { + /* @var $skeletonFieldset Mage_Eav_Model_Form_Fieldset */ + $fieldset = Mage::getModel('eav/form_fieldset'); + $fieldset->setTypeId($this->getId()) + ->setCode($skeletonFieldset->getCode()) + ->setLabels($skeletonFieldset->getLabels()) + ->setSortOrder($skeletonFieldset->getSortOrder()) + ->save(); + $fieldsetMap[$skeletonFieldset->getId()] = $fieldset->getId(); + } + + // copy elements + foreach ($elementCollection as $skeletonElement) { + /* @var $skeletonElement Mage_Eav_Model_Form_Element */ + $element = Mage::getModel('eav/form_element'); + $fieldsetId = null; + if ($skeletonElement->getFieldsetId()) { + $fieldsetId = $fieldsetMap[$skeletonElement->getFieldsetId()]; + } + $element->setTypeId($this->getId()) + ->setFieldsetId($fieldsetId) + ->setAttributeId($skeletonElement->getAttributeId()) + ->setSortOrder($skeletonElement->getSortOrder()); + } + + return $this; + } +} 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 2c54c658a8..f26893fe22 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php @@ -169,23 +169,7 @@ protected function _beforeSave(Mage_Core_Model_Abstract $object) Mage::throwException(Mage::helper('eav')->__('Frontend label is not defined')); } $object->setFrontendLabel($frontendLabel[0]); - - if ($object->getData('modulePrefix')) { - $str = $object->getData('modulePrefix') . Mage_Core_Model_Translate::SCOPE_SEPARATOR . $frontendLabel[0]; - } - else { - $str = $frontendLabel[0]; - } - Mage::getModel('core/translate_string') - ->setString($str) - ->setTranslate($frontendLabel[0]) - ->setStoreTranslations($frontendLabel) - ->save(); - } - $applyTo = $object->getApplyTo(); - - if (is_array($applyTo)) { - $object->setApplyTo(implode(',', $applyTo)); + $object->setStoreLabels($frontendLabel); } /** @@ -208,11 +192,76 @@ protected function _beforeSave(Mage_Core_Model_Abstract $object) */ protected function _afterSave(Mage_Core_Model_Abstract $object) { - $this->saveInSetIncluding($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... * @@ -392,7 +441,8 @@ public function getAttributeCodesByFrontendType($type) * @param int $store * @return Varien_Db_Select */ - public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $store) { + 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`"; @@ -409,7 +459,7 @@ public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $at . " AND t1.entity_type_id = t2.entity_type_id" . " AND t1.attribute_id = t2.attribute_id" . " AND t2.store_id = {$store}", - array($attribute->getAttributeCode() => "IFNULL(t2.value, t1.value)")) + 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); @@ -428,4 +478,58 @@ public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $at 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; + } } 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 064c1ab469..69007a41d7 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 @@ -49,14 +49,13 @@ public function _construct() } /** - * Specify select columns which are used for load arrtibute values + * Return array of fields to load attribute values * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection + * @return array */ - public function useLoadDataFields() + protected function _getLoadDataFields() { - $this->getSelect()->reset(Zend_Db_Select::COLUMNS); - $this->getSelect()->columns(array( + $fields = array( 'attribute_id', 'entity_type_id', 'attribute_code', @@ -66,8 +65,19 @@ public function useLoadDataFields() 'backend_table', 'frontend_input', 'source_model', - 'is_global' - )); + ); + 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; } @@ -80,6 +90,12 @@ public function useLoadDataFields() public function setEntityTypeFilter($typeId) { $this->getSelect()->where('main_table.entity_type_id=?', $typeId); + if ($additionalTable = $this->getResource()->getAdditionalAttributeTable($typeId)) { + $this->getSelect()->join( + array('additional_table' => $this->getTable($additionalTable)), + 'additional_table.attribute_id=main_table.attribute_id' + ); + } return $this; } @@ -193,39 +209,6 @@ public function addAttributeGrouping() return $this; } - /** - * Specify filter by "is_visible" field - * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function addVisibleFilter() - { - $this->getSelect()->where('main_table.is_visible=?', 1); - return $this; - } - - /** - * Specify "is_filterable" filter - * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function addIsFilterableFilter() - { - $this->getSelect()->where('main_table.is_filterable>0'); - return $this; - } - - /** - * Add filterable in search filter - * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function addIsFilterableInSearchFilter() - { - $this->getSelect()->where('main_table.is_filterable_in_search>0'); - return $this; - } - /** * Specify "is_unique" filter as true * @@ -248,17 +231,6 @@ public function addIsNotUniqueFilter() return $this; } - /** - * Specify "is_searchable" filter - * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function addIsSearchableFilter() - { - $this->getSelect()->where('main_table.is_searchable=1'); - return $this; - } - /** * Specify filter to select just attributes with options * @@ -276,18 +248,6 @@ public function addHasOptionsFilter() return $this; } - /** - * Specify "is_visible_in_advanced_search" filter - * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function addDisplayInAdvancedSearchFilter(){ - $this->getSelect() - ->where('main_table.is_visible_in_advanced_search = ?', 1); - - return $this; - } - /** * Apply filter by attribute frontend input type * @@ -402,4 +362,20 @@ public function setCodeFilter($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 5aefb82e0d..e4e651c825 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 @@ -82,4 +82,19 @@ private function _getMaxSortOrder($object) $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; + } } \ No newline at end of file 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 9083605d38..33a6b6082a 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 @@ -86,7 +86,7 @@ public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $at $joinCondition .= " AND `e`.`child_id`=`t1`.`entity_id`"; } - $valueExpr = new Zend_Db_Expr("IFNULL(t2.value, t1.value)"); + $valueExpr = new Zend_Db_Expr("IF(t2.value_id>0, t2.value, t1.value)"); $select = $this->_getReadAdapter()->select() ->joinLeft( array('t1' => $attributeTable), 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 6bd3b0dd6d..1bcf827de9 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 @@ -61,7 +61,7 @@ public function setStoreFilter($storeId=null, $useDefaultValue=true) ->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('IFNULL(store_value.value,store_default_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 { 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 6f97b6bb49..54f61f6a1f 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 @@ -63,6 +63,7 @@ protected function _afterSave(Mage_Core_Model_Abstract $object) /* @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) { 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 601600a0ee..40d134d94c 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Type.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Type.php @@ -55,4 +55,18 @@ 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/Form/Element.php b/app/code/core/Mage/Eav/Model/Mysql4/Form/Element.php new file mode 100644 index 0000000000..6f204cb747 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Mysql4/Form/Element.php @@ -0,0 +1,68 @@ + + */ +class Mage_Eav_Model_Mysql4_Form_Element extends Mage_Core_Model_Mysql4_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/Mysql4/Form/Element/Collection.php b/app/code/core/Mage/Eav/Model/Mysql4/Form/Element/Collection.php new file mode 100644 index 0000000000..60b91bb09f --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Mysql4/Form/Element/Collection.php @@ -0,0 +1,138 @@ + + */ +class Mage_Eav_Model_Mysql4_Form_Element_Collection extends Mage_Core_Model_Mysql4_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_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 new file mode 100644 index 0000000000..e0d603d35d --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Mysql4/Form/Fieldset.php @@ -0,0 +1,161 @@ + + */ +class Mage_Eav_Model_Mysql4_Form_Fieldset extends Mage_Core_Model_Mysql4_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_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 new file mode 100644 index 0000000000..4218e3b445 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Mysql4/Form/Fieldset/Collection.php @@ -0,0 +1,129 @@ + + */ +class Mage_Eav_Model_Mysql4_Form_Fieldset_Collection extends Mage_Core_Model_Mysql4_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_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 new file mode 100644 index 0000000000..0afc6b3de9 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Mysql4/Form/Type.php @@ -0,0 +1,147 @@ + + */ +class Mage_Eav_Model_Mysql4_Form_Type extends Mage_Core_Model_Mysql4_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_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 new file mode 100644 index 0000000000..25039f229d --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Mysql4/Form/Type/Collection.php @@ -0,0 +1,76 @@ + + */ +class Mage_Eav_Model_Mysql4_Form_Type_Collection extends Mage_Core_Model_Mysql4_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_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/etc/config.xml b/app/code/core/Mage/Eav/etc/config.xml index d5795c5cbc..aaa0286d8a 100644 --- a/app/code/core/Mage/Eav/etc/config.xml +++ b/app/code/core/Mage/Eav/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.13 + 0.7.15 @@ -41,19 +41,54 @@ Mage_Eav_Model_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_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 + @@ -64,26 +99,28 @@ Mage_Eav Mage_Eav_Model_Entity_Setup - - core_setup - - - - core_write - - - - - core_read - - EAV types and attributes + + + + + 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 new file mode 100644 index 0000000000..d8397d7083 --- /dev/null +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.13-0.7.14.php @@ -0,0 +1,48 @@ +startSetup(); +$installer->getConnection()->addColumn($installer->getTable('eav/entity_type'), 'additional_attribute_table', 'varchar(255) NOT NULL DEFAULT \'\''); +$installer->getConnection()->addColumn($installer->getTable('eav/entity_type'), 'entity_attribute_collection', 'varchar(255) NOT NULL DEFAULT \'\''); +$installer->run(" + CREATE TABLE `{$installer->getTable('eav/attribute_label')}` ( + `attribute_label_id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `attribute_id` smallint(5) unsigned NOT NULL DEFAULT '0', + `store_id` smallint(5) unsigned NOT NULL DEFAULT '0', + `value` varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY (`attribute_label_id`), + KEY `IDX_ATTRIBUTE_LABEL_ATTRIBUTE` (`attribute_id`), + KEY `IDX_ATTRIBUTE_LABEL_STORE` (`store_id`), + KEY `IDX_ATTRIBUTE_LABEL_ATTRIBUTE_STORE` (`attribute_id`, `store_id`), + CONSTRAINT `FK_ATTRIBUTE_LABEL_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav/attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_ATTRIBUTE_LABEL_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core/store')}` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +"); +$installer->endSetup(); 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 new file mode 100644 index 0000000000..99fe7ae27b --- /dev/null +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.14-0.7.15.php @@ -0,0 +1,97 @@ +startSetup(); + +$installer->run(" + +CREATE TABLE `{$installer->getTable('eav/form_type')}` ( + `type_id` smallint(5) unsigned NOT NULL auto_increment, + `code` char(64) NOT NULL, + `label` varchar(255) NOT NULL, + `is_system` tinyint(1) unsigned NOT NULL default '0', + `theme` varchar(64) NOT NULL default '', + `store_id` smallint(5) unsigned NOT NULL, + PRIMARY KEY (`type_id`), + UNIQUE KEY `UNQ_FORM_TYPE_CODE` (`code`, `theme`, `store_id`), + KEY `IDX_STORE` (`store_id`), + CONSTRAINT `FK_EAV_FORM_TYPE_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core/store')}` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `{$installer->getTable('eav/form_type_entity')}` ( + `type_id` smallint(5) unsigned NOT NULL, + `entity_type_id` smallint(5) unsigned NOT NULL, + PRIMARY KEY (`type_id`,`entity_type_id`), + KEY `IDX_EAV_ENTITY_TYPE` (`entity_type_id`), + CONSTRAINT `FK_EAV_FORM_TYPE_ENTITY_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$installer->getTable('eav/entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_EAV_FORM_TYPE_ENTITY_FORM_TYPE` FOREIGN KEY (`type_id`) REFERENCES `{$installer->getTable('eav/form_type')}` (`type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `{$installer->getTable('eav/form_fieldset')}` ( + `fieldset_id` smallint(5) unsigned NOT NULL auto_increment, + `type_id` smallint(5) unsigned NOT NULL, + `code` char(64) NOT NULL, + `sort_order` int(11) NOT NULL default '0', + PRIMARY KEY (`fieldset_id`), + UNIQUE KEY `UNQ_FORM_FIELDSET_CODE` (`type_id`,`code`), + KEY `IDX_FORM_TYPE` (`type_id`), + CONSTRAINT `FK_EAV_FORM_FIELDSET_FORM_TYPE` FOREIGN KEY (`type_id`) REFERENCES `{$installer->getTable('eav/form_type')}` (`type_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `{$installer->getTable('eav/form_fieldset_label')}` ( + `fieldset_id` smallint(5) unsigned NOT NULL, + `store_id` smallint(5) unsigned NOT NULL, + `label` varchar(255) NOT NULL, + PRIMARY KEY (`fieldset_id`, `store_id`), + KEY `IDX_FORM_FIELDSET` (`fieldset_id`), + KEY `IDX_STORE` (`store_id`), + CONSTRAINT `FK_EAV_FORM_FIELDSET_LABEL_FORM_FIELDSET` FOREIGN KEY (`fieldset_id`) REFERENCES `{$installer->getTable('eav/form_fieldset')}` (`fieldset_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_EAV_FORM_FIELDSET_LABEL_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core/store')}` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `{$installer->getTable('eav_form_element')}` ( + `element_id` int(10) unsigned NOT NULL auto_increment, + `type_id` smallint(5) unsigned NOT NULL, + `fieldset_id` smallint(5) unsigned default NULL, + `attribute_id` smallint(5) unsigned NOT NULL, + `sort_order` int(11) NOT NULL default '0', + PRIMARY KEY (`element_id`), + KEY `IDX_FORM_TYPE` (`type_id`), + UNIQUE KEY `UNQ_FORM_ATTRIBUTE` (`type_id`,`attribute_id`), + KEY `IDX_FORM_FIELDSET` (`fieldset_id`), + KEY `IDX_FORM_ATTRIBUTE` (`attribute_id`), + CONSTRAINT `FK_EAV_FORM_ELEMENT_FORM_TYPE` FOREIGN KEY (`type_id`) REFERENCES `{$installer->getTable('eav/form_type')}` (`type_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_EAV_FORM_ELEMENT_FORM_FIELDSET` FOREIGN KEY (`fieldset_id`) REFERENCES `{$installer->getTable('eav/form_fieldset')}` (`fieldset_id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `FK_EAV_FORM_ELEMENT_ATTRIBUTE` FOREIGN KEY (`attribute_id`) REFERENCES `{$installer->getTable('eav/attribute')}` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +"); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Eway/etc/config.xml b/app/code/core/Mage/Eway/etc/config.xml index 4544ded182..4ccaf10d87 100644 --- a/app/code/core/Mage/Eway/etc/config.xml +++ b/app/code/core/Mage/Eway/etc/config.xml @@ -50,20 +50,7 @@ Mage_Eway Mage_Eway_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - Mage_Eway_Block diff --git a/app/code/core/Mage/Flo2Cash/etc/config.xml b/app/code/core/Mage/Flo2Cash/etc/config.xml index ca43c21b55..b016454409 100644 --- a/app/code/core/Mage/Flo2Cash/etc/config.xml +++ b/app/code/core/Mage/Flo2Cash/etc/config.xml @@ -50,20 +50,7 @@ Mage_Flo2Cash Mage_Flo2Cash_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - Mage_Flo2Cash_Block diff --git a/app/code/core/Mage/GiftMessage/etc/config.xml b/app/code/core/Mage/GiftMessage/etc/config.xml index ef3f3f0cdd..46edc99418 100644 --- a/app/code/core/Mage/GiftMessage/etc/config.xml +++ b/app/code/core/Mage/GiftMessage/etc/config.xml @@ -58,20 +58,7 @@ Mage_GiftMessage Mage_GiftMessage_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - diff --git a/app/code/core/Mage/GiftRegistry/etc/config.xml b/app/code/core/Mage/GiftRegistry/etc/config.xml index 9e818cc486..da4eb9ab57 100644 --- a/app/code/core/Mage/GiftRegistry/etc/config.xml +++ b/app/code/core/Mage/GiftRegistry/etc/config.xml @@ -61,20 +61,7 @@ Mage_GiftRegistry - - core_setup - - - - core_write - - - - - core_read - - diff --git a/app/code/core/Mage/GoogleAnalytics/Model/Observer.php b/app/code/core/Mage/GoogleAnalytics/Model/Observer.php index 9851120398..a9e5d1ebf5 100644 --- a/app/code/core/Mage/GoogleAnalytics/Model/Observer.php +++ b/app/code/core/Mage/GoogleAnalytics/Model/Observer.php @@ -35,11 +35,22 @@ class Mage_GoogleAnalytics_Model_Observer { /** - * Enter description here... + * Create Google Analytics block for success page view * - * @param unknown_type $observer + * @deprecated after 1.3.2.3 Use setGoogleAnalyticsOnOrderSuccessPageView() method instead + * @param Varien_Event_Observer $observer */ public function order_success_page_view($observer) + { + $this->setGoogleAnalyticsOnOrderSuccessPageView($observer); + } + + /** + * Create Google Analytics block for success page view + * + * @param Varien_Event_Observer $observer + */ + public function setGoogleAnalyticsOnOrderSuccessPageView(Varien_Event_Observer $observer) { $quoteId = Mage::getSingleton('checkout/session')->getLastQuoteId(); $analyticsBlock = Mage::app()->getFrontController()->getAction()->getLayout()->getBlock('google_analytics'); diff --git a/app/code/core/Mage/GoogleAnalytics/etc/adminhtml.xml b/app/code/core/Mage/GoogleAnalytics/etc/adminhtml.xml new file mode 100644 index 0000000000..cbfb8ca05c --- /dev/null +++ b/app/code/core/Mage/GoogleAnalytics/etc/adminhtml.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + Google API + + + + + + + + + + diff --git a/app/code/core/Mage/GoogleAnalytics/etc/config.xml b/app/code/core/Mage/GoogleAnalytics/etc/config.xml index c997e27152..d4472ca974 100644 --- a/app/code/core/Mage/GoogleAnalytics/etc/config.xml +++ b/app/code/core/Mage/GoogleAnalytics/etc/config.xml @@ -60,12 +60,19 @@ - singleton googleanalytics/observer - order_success_page_view + setGoogleAnalyticsOnOrderSuccessPageView + + + + googleanalytics/observer + setGoogleAnalyticsOnOrderSuccessPageView + + + @@ -86,24 +93,5 @@ - - - - - - - - - - Google API - - - - - - - - - \ No newline at end of file diff --git a/app/code/core/Mage/GoogleBase/Model/Service/Item.php b/app/code/core/Mage/GoogleBase/Model/Service/Item.php index 627f296cb0..3079398f73 100644 --- a/app/code/core/Mage/GoogleBase/Model/Service/Item.php +++ b/app/code/core/Mage/GoogleBase/Model/Service/Item.php @@ -293,6 +293,7 @@ protected function _setUniversalData() $this->_setAttribute('image_link', $object->getData('image_url'), 'url'); } + $this->_setAttribute('condition', 'new', 'text'); $this->_setAttribute('target_country', $targetCountry, 'text'); $this->_setAttribute('item_language', $this->getConfig()->getCountryInfo($targetCountry, 'language'), 'text'); diff --git a/app/code/core/Mage/GoogleBase/etc/adminhtml.xml b/app/code/core/Mage/GoogleBase/etc/adminhtml.xml new file mode 100644 index 0000000000..106f93d669 --- /dev/null +++ b/app/code/core/Mage/GoogleBase/etc/adminhtml.xml @@ -0,0 +1,74 @@ + + + + + + + + Google Base + + + Manage Attributes + googlebase/types + + + Manage Items + googlebase/items + + + + + + + + + + + + + + Google Base + 500 + + + Manage Attributes + 0 + + + Manage Items + 5 + + + + + + + + + + diff --git a/app/code/core/Mage/GoogleBase/etc/config.xml b/app/code/core/Mage/GoogleBase/etc/config.xml index 4689f2b5ca..3f92f83588 100644 --- a/app/code/core/Mage/GoogleBase/etc/config.xml +++ b/app/code/core/Mage/GoogleBase/etc/config.xml @@ -62,20 +62,7 @@ Mage_GoogleBase - - core_setup - - - - core_write - - - - - core_read - - Mage_GoogleBase_Block @@ -106,7 +93,6 @@ - singleton googlebase/observer saveProductItem @@ -115,7 +101,6 @@ - singleton googlebase/observer deleteProductItem @@ -129,51 +114,6 @@ - - - - - Google Base - - - Manage Attributes - googlebase/types - - - Manage Items - googlebase/items - - - - - - - - - - - - - - Google Base - 500 - - - Manage Attributes - 0 - - - Manage Items - 5 - - - - - - - - - diff --git a/app/code/core/Mage/GoogleCheckout/Model/Mysql4/Setup.php b/app/code/core/Mage/GoogleCheckout/Model/Mysql4/Setup.php index 94087326c5..6e856b2ee3 100644 --- a/app/code/core/Mage/GoogleCheckout/Model/Mysql4/Setup.php +++ b/app/code/core/Mage/GoogleCheckout/Model/Mysql4/Setup.php @@ -25,7 +25,7 @@ */ -class Mage_GoogleCheckout_Model_Mysql4_Setup extends Mage_Sales_Model_Mysql4_Setup +class Mage_GoogleCheckout_Model_Mysql4_Setup extends Mage_Catalog_Model_Resource_Eav_Mysql4_Setup { } \ No newline at end of file diff --git a/app/code/core/Mage/GoogleCheckout/Model/Payment.php b/app/code/core/Mage/GoogleCheckout/Model/Payment.php index ba8d7d890e..7f2946b2c5 100644 --- a/app/code/core/Mage/GoogleCheckout/Model/Payment.php +++ b/app/code/core/Mage/GoogleCheckout/Model/Payment.php @@ -56,17 +56,6 @@ public function canEdit() return false; } - /** - * Return true if the method can be used at this time - * Use google/checkout/active flag of admin module config - * - * @return bool - */ - public function isAvailable($quote=null) - { - return Mage::getStoreConfig('google/checkout/active') > 0; - } - /** * Return Order Place Redirect URL * diff --git a/app/code/core/Mage/GoogleCheckout/etc/adminhtml.xml b/app/code/core/Mage/GoogleCheckout/etc/adminhtml.xml new file mode 100644 index 0000000000..c8ffa3e2f4 --- /dev/null +++ b/app/code/core/Mage/GoogleCheckout/etc/adminhtml.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + Google API + + + + + + + + + + diff --git a/app/code/core/Mage/GoogleCheckout/etc/config.xml b/app/code/core/Mage/GoogleCheckout/etc/config.xml index e1c8c802c3..2aeb2c5f9b 100644 --- a/app/code/core/Mage/GoogleCheckout/etc/config.xml +++ b/app/code/core/Mage/GoogleCheckout/etc/config.xml @@ -33,13 +33,13 @@ - - + + - - + + @@ -64,20 +64,7 @@ Mage_GoogleCheckout Mage_GoogleCheckout_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - Mage_GoogleCheckout_Block @@ -137,25 +124,6 @@ Something like this is to be added to resolve bug #4890 - - - - - - - - - - Google API - - - - - - - - - diff --git a/app/code/core/Mage/GoogleOptimizer/Block/Adminhtml/Cms/Page/Edit/Enable.php b/app/code/core/Mage/GoogleOptimizer/Block/Adminhtml/Cms/Page/Edit/Enable.php new file mode 100644 index 0000000000..934935924b --- /dev/null +++ b/app/code/core/Mage/GoogleOptimizer/Block/Adminhtml/Cms/Page/Edit/Enable.php @@ -0,0 +1,80 @@ + + */ +class Mage_Googleoptimizer_Block_Adminhtml_Cms_Page_Edit_Enable extends Mage_Adminhtml_Block_Template +{ + /** + * Utility method to call method of specified block + * in case google optimizer enabled for cms in the system. + * Uses as parameters block name, method name and params for method. + * + * @param string $name + * @param string $method + * @param array $params + * @return Mage_Googleoptimizer_Block_Adminhtml_Cms_Page_Edit_Enable + */ + public function ifGoogleOptimizerEnabled($name, $method, $params = array()) + { + if (Mage::helper('googleoptimizer')->isOptimizerActiveForCms()) { + $block = $this->getLayout()->getBlock($name); + if ($block) { + call_user_func_array(array($block, $method), $params); + } + } + + return $this; + } + + /** + * in case google optimizer enabled for cms in the system. + * Uses as parameters container name, block name, type and attributes + * + * @param string $container + * @param string $name + * @param string $type + * @param string $attributes + * @return Mage_Googleoptimizer_Block_Adminhtml_Cms_Page_Edit_Enable + */ + public function ifGoogleOptimizerEnabledAppend($container, $name, $type, $attributes = array()) + { + if (Mage::helper('googleoptimizer')->isOptimizerActiveForCms()) { + $containerBlock = $this->getLayout()->getBlock($container); + if ($containerBlock) { + $block = $this->getLayout()->createBlock($type, $name, $attributes); + $containerBlock->append($block); + } + } + + return $this; + } +} diff --git a/app/code/core/Mage/GoogleOptimizer/Block/Adminhtml/Cms/Page/Edit/Tab/Googleoptimizer.php b/app/code/core/Mage/GoogleOptimizer/Block/Adminhtml/Cms/Page/Edit/Tab/Googleoptimizer.php index 52c0de1700..da8c0aedb5 100644 --- a/app/code/core/Mage/GoogleOptimizer/Block/Adminhtml/Cms/Page/Edit/Tab/Googleoptimizer.php +++ b/app/code/core/Mage/GoogleOptimizer/Block/Adminhtml/Cms/Page/Edit/Tab/Googleoptimizer.php @@ -31,7 +31,9 @@ * @package Mage_GoogleOptimizer * @author Magento Core Team */ -class Mage_Googleoptimizer_Block_Adminhtml_Cms_Page_Edit_Tab_Googleoptimizer extends Mage_Adminhtml_Block_Widget_Form +class Mage_Googleoptimizer_Block_Adminhtml_Cms_Page_Edit_Tab_Googleoptimizer + extends Mage_Adminhtml_Block_Widget_Form + implements Mage_Adminhtml_Block_Widget_Tab_Interface { protected function _prepareForm() { @@ -43,6 +45,15 @@ protected function _prepareForm() Mage::helper('googleoptimizer')->setStoreId(Mage::app()->getDefaultStoreView()); + /* + * Checking if user have permissions to save information + */ + if ($this->_isAllowedAction('save')) { + $isElementDisabled = false; + } else { + $isElementDisabled = true; + } + $fieldset->addField('conversion_page', 'select', array( 'name' => 'conversion_page', @@ -50,7 +61,8 @@ protected function _prepareForm() 'values'=> Mage::getModel('googleoptimizer/adminhtml_system_config_source_googleoptimizer_conversionpages')->toOptionArray(), 'class' => 'select googleoptimizer validate-googleoptimizer', 'required' => false, - 'onchange' => 'googleOptimizerConversionPageAction(this)' + 'onchange' => 'googleOptimizerConversionPageAction(this)', + 'disabled' => $isElementDisabled ) ); @@ -60,6 +72,7 @@ protected function _prepareForm() array( 'name' => 'conversion_page_url', 'label' => Mage::helper('googleoptimizer')->__('Conversion Page URL'), + 'disabled' => $isElementDisabled ) )->setRenderer($this->getLayout()->createBlock('googleoptimizer/adminhtml_cms_page_edit_renderer_conversion')); } else { @@ -70,7 +83,8 @@ protected function _prepareForm() 'class' => 'input-text', 'readonly' => 'readonly', 'required' => false, - 'note' => Mage::helper('googleoptimizer')->__('Please copy and paste this value to experiment edit form') + 'note' => Mage::helper('googleoptimizer')->__('Please copy and paste this value to experiment edit form'), + 'disabled' => $isElementDisabled ) ); } @@ -78,6 +92,7 @@ protected function _prepareForm() $fieldset->addField('export_controls', 'text', array( 'name' => 'export_controls', + 'disabled' => $isElementDisabled ) ); @@ -94,7 +109,8 @@ protected function _prepareForm() 'values'=> $pageTypes, 'class' => 'select googleoptimizer validate-googleoptimizer', 'required' => false, - 'onchange' => 'googleOptimizerVariantPageAction(this)' + 'onchange' => 'googleOptimizerVariantPageAction(this)', + 'disabled' => $isElementDisabled ) ); @@ -105,6 +121,7 @@ protected function _prepareForm() 'class' => 'textarea validate-googleoptimizer', 'required' => false, 'note' => '', + 'disabled' => $isElementDisabled ) ); $fieldset->addField('tracking_script', 'textarea', @@ -114,6 +131,7 @@ protected function _prepareForm() 'class' => 'textarea validate-googleoptimizer', 'required' => false, 'note' => '', + 'disabled' => $isElementDisabled ) ); $fieldset->addField('conversion_script', 'textarea', @@ -123,6 +141,7 @@ protected function _prepareForm() 'class' => 'textarea validate-googleoptimizer', 'required' => false, 'note' => '', + 'disabled' => $isElementDisabled ) ); @@ -175,4 +194,54 @@ public function getGoogleOptimizer() } return $googleOptimizer; } -} \ No newline at end of file + + /** + * Prepare label for tab + * + * @return string + */ + public function getTabLabel() + { + return Mage::helper('googleoptimizer')->__('Page View Optimization'); + } + + /** + * Prepare title for tab + * + * @return string + */ + public function getTabTitle() + { + return Mage::helper('googleoptimizer')->__('Page View Optimization'); + } + + /** + * Returns status flag about this tab can be showen or not + * + * @return true + */ + public function canShowTab() + { + return true; + } + + /** + * Returns status flag about this tab hidden or not + * + * @return true + */ + public function isHidden() + { + return false; + } + + /** Check permission for passed action + * + * @param string $action + * @return bool + */ + protected function _isAllowedAction($action) + { + return Mage::getSingleton('admin/session')->isAllowed('cms/page/' . $action); + } +} diff --git a/app/code/core/Mage/GoogleOptimizer/etc/config.xml b/app/code/core/Mage/GoogleOptimizer/etc/config.xml index 9f3b8ae5f8..eec8303a9b 100644 --- a/app/code/core/Mage/GoogleOptimizer/etc/config.xml +++ b/app/code/core/Mage/GoogleOptimizer/etc/config.xml @@ -57,20 +57,7 @@ Mage_GoogleOptimizer - - core_setup - - - - core_write - - - - - core_read - - Mage_GoogleOptimizer_Block @@ -79,7 +66,6 @@ - singleton googleoptimizer/observer appendToPageGoogleOptimizerScripts @@ -125,7 +111,6 @@ - singleton googleoptimizer/observer appendToProductGoogleOptimizerScripts @@ -134,7 +119,6 @@ - singleton googleoptimizer/observer appendToCategoryGoogleOptimizerScripts @@ -143,7 +127,6 @@ - singleton googleoptimizer/observer assignHandlers @@ -166,7 +149,6 @@ - singleton googleoptimizer/observer appendToProductGoogleOptimizerScripts @@ -175,7 +157,6 @@ - singleton googleoptimizer/observer prepareProductGoogleOptimizerScripts @@ -184,7 +165,6 @@ - singleton googleoptimizer/observer saveProductGoogleOptimizerScripts @@ -193,7 +173,6 @@ - singleton googleoptimizer/observer deleteProductGoogleOptimizerScripts @@ -202,7 +181,6 @@ - singleton googleoptimizer/observer appendToCategoryGoogleOptimizerScripts @@ -211,7 +189,6 @@ - singleton googleoptimizer/observer prepareCategoryGoogleOptimizerScripts @@ -220,7 +197,6 @@ - singleton googleoptimizer/observer saveCategoryGoogleOptimizerScripts @@ -229,7 +205,6 @@ - singleton googleoptimizer/observer deleteCategoryGoogleOptimizerScripts @@ -238,7 +213,6 @@ - singleton googleoptimizer/observer appendToPageGoogleOptimizerScripts @@ -247,7 +221,6 @@ - singleton googleoptimizer/observer preparePageGoogleOptimizerScripts @@ -256,7 +229,6 @@ - singleton googleoptimizer/observer savePageGoogleOptimizerScripts @@ -265,7 +237,6 @@ - singleton googleoptimizer/observer deletePageGoogleOptimizerScripts diff --git a/app/code/core/Mage/Ideal/etc/config.xml b/app/code/core/Mage/Ideal/etc/config.xml index fd664508cc..de8f463559 100755 --- a/app/code/core/Mage/Ideal/etc/config.xml +++ b/app/code/core/Mage/Ideal/etc/config.xml @@ -50,20 +50,7 @@ Mage_Ideal Mage_Ideal_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - Mage_Ideal_Block @@ -103,7 +90,6 @@ - singleton ideal/observer convertPayment diff --git a/app/code/core/Mage/Index/Block/Adminhtml/Notifications.php b/app/code/core/Mage/Index/Block/Adminhtml/Notifications.php new file mode 100644 index 0000000000..6e9404778a --- /dev/null +++ b/app/code/core/Mage/Index/Block/Adminhtml/Notifications.php @@ -0,0 +1,55 @@ +getProcessesCollection(); + foreach ($processes as $process) { + if ($process->getStatus() == Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX) { + $res[] = $process->getIndexer()->getName(); + } + } + return $res; + } + + /** + * Get index management url + * + * @return string + */ + public function getManageUrl() + { + return $this->getUrl('adminhtml/process/list'); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Index/Block/Adminhtml/Process.php b/app/code/core/Mage/Index/Block/Adminhtml/Process.php new file mode 100644 index 0000000000..377e443990 --- /dev/null +++ b/app/code/core/Mage/Index/Block/Adminhtml/Process.php @@ -0,0 +1,37 @@ +_blockGroup = 'index'; + $this->_controller = 'adminhtml_process'; + $this->_headerText = Mage::helper('index')->__('Index Management'); + parent::__construct(); + $this->_removeButton('add'); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit.php b/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit.php new file mode 100644 index 0000000000..87ee4c7d56 --- /dev/null +++ b/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit.php @@ -0,0 +1,81 @@ +_objectId = 'process_id'; + $this->_controller = 'adminhtml_process'; + $this->_blockGroup = 'index'; + + parent::__construct(); + + $this->_updateButton('save', 'label', Mage::helper('cms')->__('Save Process')); + $this->_addButton('reindex', array( + 'label' => Mage::helper('index')->__('Run Index Process'), + 'onclick' => "setLocation('{$this->getRunUrl()}')" + )); + $this->_removeButton('reset'); + $this->_removeButton('delete'); + } + + /** + * Get back button url + * + * @return string + */ + public function getBackUrl() + { + return $this->getUrl('adminhtml/process/list'); + } + + /** + * Get process reindex action url + * + * @return string + */ + public function getRunUrl() + { + return $this->getUrl('adminhtml/process/reindexProcess', array( + 'process' => Mage::registry('current_index_process')->getId() + )); + } + + /** + * Retrieve text for header element depending on loaded page + * + * @return string + */ + public function getHeaderText() + { + $process = Mage::registry('current_index_process'); + if ($process && $process->getId()) { + return Mage::helper('index')->__("'%s' Index Process Information", $process->getIndexer()->getName()); + } + } +} 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 new file mode 100644 index 0000000000..4341419617 --- /dev/null +++ b/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Form.php @@ -0,0 +1,41 @@ + 'edit_form', 'action' => $this->getActionUrl(), 'method' => 'post')); + $form->setUseContainer(true); + $this->setForm($form); + return parent::_prepareForm(); + } + + public function getActionUrl() + { + return $this->getUrl('adminhtml/process/save'); + } +} 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 new file mode 100644 index 0000000000..d88af07c73 --- /dev/null +++ b/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Tab/Main.php @@ -0,0 +1,118 @@ +setHtmlIdPrefix('index_process_'); + $fieldset = $form->addFieldset( + 'base_fieldset', + array('legend'=>Mage::helper('index')->__('General'), 'class'=>'fieldset-wide') + ); + + $fieldset->addField('process_id', 'hidden', array('name' => 'process', 'value'=>$model->getId())); + + $fieldset->addField('name', 'note', array( + 'label' => Mage::helper('index')->__('Index Name'), + 'title' => Mage::helper('index')->__('Index Name'), + 'text' => ''.$model->getIndexer()->getName().'' + )); + + $fieldset->addField('description', 'note', array( + 'label' => Mage::helper('index')->__('Index Description'), + 'title' => Mage::helper('index')->__('Index Description'), + 'text' => $model->getIndexer()->getDescription() + )); + + $fieldset->addField('mode', 'select', array( + 'label' => Mage::helper('index')->__('Index Mode'), + 'title' => Mage::helper('index')->__('Index Mode'), + 'name' => 'mode', + 'value' => $model->getMode(), + 'values'=> $model->getModesOptions() + )); + + //$form->setValues($model->getData()); + $this->setForm($form); + return parent::_prepareForm(); + } + + /** + * Prepare label for tab + * + * @return string + */ + public function getTabLabel() + { + return Mage::helper('index')->__('Process Information'); + } + + /** + * Prepare title for tab + * + * @return string + */ + public function getTabTitle() + { + return Mage::helper('index')->__('Process Information'); + } + + /** + * Returns status flag about this tab can be showen or not + * + * @return true + */ + public function canShowTab() + { + return true; + } + + /** + * Returns status flag about this tab hidden or not + * + * @return true + */ + public function isHidden() + { + return false; + } + + /** + * Check permission for passed action + * + * @param string $action + * @return bool + */ + protected function _isAllowedAction($action) + { + return true; + } +} 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 new file mode 100644 index 0000000000..8886d763ec --- /dev/null +++ b/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Tabs.php @@ -0,0 +1,37 @@ +setId('process_tabs'); + $this->setDestElementId('edit_form'); + $this->setTitle(Mage::helper('index')->__('Index')); + } +} diff --git a/app/code/core/Mage/Index/Block/Adminhtml/Process/Grid.php b/app/code/core/Mage/Index/Block/Adminhtml/Process/Grid.php new file mode 100644 index 0000000000..4c2cedc429 --- /dev/null +++ b/app/code/core/Mage/Index/Block/Adminhtml/Process/Grid.php @@ -0,0 +1,213 @@ +_processModel = Mage::getModel('index/process'); + $this->setId('indexer_processes_grid'); + $this->_filterVisibility = false; + $this->_pagerVisibility = false; + } + + /** + * Prepare grid collection + */ + protected function _prepareCollection() + { + $collection = Mage::getResourceModel('index/process_collection'); + $this->setCollection($collection); + return parent::_prepareCollection(); + } + + /** + * Add name and description to collection elements + */ + protected function _afterLoadCollection() + { + foreach ($this->_collection as $item) { + $item->setName($item->getIndexer()->getName()); + $item->setDescription($item->getIndexer()->getDescription()); + } + return $this; + } + + /** + * Prepare grid columns + */ + protected function _prepareColumns() + { + $baseUrl = $this->getUrl(); + $this->addColumn('indexer_code', array( + 'header' => Mage::helper('index')->__('Index'), + 'width' => '180', + 'align' => 'left', + 'index' => 'name', + 'sortable' => false, + )); + + $this->addColumn('description', array( + 'header' => Mage::helper('index')->__('Description'), + 'align' => 'left', + 'index' => 'description', + 'sortable' => false, + )); + + $this->addColumn('mode', array( + 'header' => Mage::helper('index')->__('Mode'), + 'width' => '150', + 'align' => 'left', + 'index' => 'mode', + 'type' => 'options', + 'options' => $this->_processModel->getModesOptions() + )); + + $this->addColumn('status', array( + 'header' => Mage::helper('index')->__('Status'), + 'width' => '120', + 'align' => 'left', + 'index' => 'status', + 'type' => 'options', + 'options' => $this->_processModel->getStatusesOptions(), + 'frame_callback' => array($this, 'decorateStatus') + )); + + $this->addColumn('ended_at', array( + 'header' => Mage::helper('index')->__('Last Run'), + 'type' => 'datetime', + 'width' => '180', + 'align' => 'left', + 'index' => 'ended_at', + 'frame_callback' => array($this, 'decorateDate') + )); + + $this->addColumn('action', + array( + 'header' => Mage::helper('index')->__('Action'), + 'width' => '100', + 'type' => 'action', + 'getter' => 'getId', + 'actions' => array( + array( + 'caption' => Mage::helper('index')->__('Reindex Data'), + 'url' => array('base'=> '*/*/reindexProcess'), + 'field' => 'process' + ), +// array( +// 'caption' => Mage::helper('index')->__('Pending Events'), +// 'url' => array('base'=> '*/*/reindexEvents'), +// 'field' => 'process' +// ) + ), + 'filter' => false, + 'sortable' => false, + 'is_system' => true, + )); + + return parent::_prepareColumns(); + } + + /** + * Decorate status column values + * + * @return string + */ + public function decorateStatus($value, $row, $column, $isExport) + { + $class = ''; + switch ($row->getStatus()) { + case Mage_Index_Model_Process::STATUS_PENDING : + $class = 'grid-severity-notice'; + break; + case Mage_Index_Model_Process::STATUS_RUNNING : + $class = 'grid-severity-major'; + break; + case Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX : + $class = 'grid-severity-critical'; + break; + } + return ''.$value.''; + } + + /** + * Decorate last run date coumn + * + * @return string + */ + public function decorateDate($value, $row, $column, $isExport) + { + if(!$value) { + return $this->__('Never'); + } + return $value; + } + + /** + * Get row edit url + * + * @return string + */ + public function getRowUrl($row) + { + return $this->getUrl('*/*/edit', array('process'=>$row->getId())); + } + + protected function _prepareMassaction() + { + $this->setMassactionIdField('process_id'); + $this->getMassactionBlock()->setFormFieldName('process'); + + $modeOptions = Mage::getModel('index/process')->getModesOptions(); + + $this->getMassactionBlock()->addItem('change_mode', array( + 'label' => Mage::helper('index')->__('Change Index Mode'), + 'url' => $this->getUrl('*/*/massChangeMode'), + 'additional' => array( + 'mode' => array( + 'name' => 'index_mode', + 'type' => 'select', + 'class' => 'required-entry', + 'label' => Mage::helper('index')->__('Index mode'), + 'values' => $modeOptions + ) + ) + )); + + $this->getMassactionBlock()->addItem('reindex', array( + 'label' => Mage::helper('index')->__('Reindex Data'), + 'url' => $this->getUrl('*/*/massReindex') + )); + + return $this; + } +} diff --git a/app/code/core/Mage/Index/Helper/Data.php b/app/code/core/Mage/Index/Helper/Data.php new file mode 100644 index 0000000000..0e22c6c8cd --- /dev/null +++ b/app/code/core/Mage/Index/Helper/Data.php @@ -0,0 +1,30 @@ +_init('index/event'); + } + + /** + * Specify process object + * + * @param null|Mage_Index_Model_Process $process + */ + public function setProcess($process) + { + $this->_process = $process; + return $this; + } + + /** + * Get related process object + * + * @return Mage_Index_Model_Process | null + */ + public function getProcess() + { + return $this->_process; + } + + /** + * Specify namespace for old and new data + */ + public function setDataNamespace($namespace) + { + $this->_dataNamespace = $namespace; + return $this; + } + + /** + * Reset old and new data arrays + * + * @return Mage_Index_Model_Event + */ + 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; + } + + /** + * Add process id to event object + * + * @param $processId + * @return Mage_Index_Model_Event + */ + public function addProcessId($processId, $status=Mage_Index_Model_Process::EVENT_STATUS_NEW) + { + $this->_processIds[$processId] = $status; + return $this; + } + + /** + * Get event process ids + * + * @return array + */ + public function getProcessIds() + { + return $this->_processIds; + } + + /** + * Merge previous event data to object. + * Used for events duplicated protection + * + * @param array $data + * @return Mage_Index_Model_Event + */ + public function mergePreviousData($data) + { + if (!empty($data['event_id'])) { + $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); + $this->setNewData(serialize($currentNewData)); + } + return $this; + } + + /** + * Get event old data array + * + * @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; + } + + /** + * Get event new data array + * + * @return array + */ + public function getNewData($useNamespace = true) + { + $data = $this->_getData('new_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; + } + + /** + * Add new values to old data array (overwrite if value with same key exist) + * + * @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; + } + + /** + * Add new values to new data array (overwrite if value with same key exist) + * + * @param array | string $data + * @param null | mixed $value + * @return Mage_Index_Model_Event + */ + public function addNewData($key, $value=null) + { + $newData = $this->getNewData(false); + if (!is_array($key)) { + $key = array($key => $value); + } + if ($this->_dataNamespace) { + if (!isset($newData[$this->_dataNamespace])) { + $newData[$this->_dataNamespace] = array(); + } + $newData[$this->_dataNamespace] = array_merge($newData[$this->_dataNamespace], $key); + } else { + $newData = array_merge($newData, $key); + } + $this->setNewData($newData); + return $this; + } + + /** + * Get event entity code. + * Entity code declare what kind of data object related with event (product, category etc.) + * + * @return string + */ + public function getEntity() + { + return $this->_getData('entity'); + } + + /** + * Get event action type. + * Data related on self::TYPE_* constants + * + * @return string + */ + public function getType() + { + return $this->_getData('type'); + } + + /** + * Serelaize old and new data arrays before saving + * + * @return Mage_Index_Model_Event + */ + 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)); + } + return parent::_beforeSave(); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Index/Model/Indexer.php b/app/code/core/Mage/Index/Model/Indexer.php new file mode 100644 index 0000000000..9ea3761135 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Indexer.php @@ -0,0 +1,214 @@ +_processesCollection = Mage::getResourceModel('index/process_collection'); + } + + /** + * Get collection of all available processes + * + * @return Mage_Index_Model_Mysql4_Process_Collection + */ + public function getProcessesCollection() + { + return $this->_processesCollection; + } + + /** + * Get index process by specific code + * + * @param string $code + * @return Mage_Index_Model_Process | false + */ + public function getProcessByCode($code) + { + foreach ($this->_processesCollection as $process) { + if ($process->getIndexerCode() == $code) { + return $process; + } + } + return false; + } + + /** + * Lock indexer actions + */ + public function lockIndexer() + { + $this->_lockFlag = true; + return $this; + } + + /** + * Unlock indexer actions + */ + public function unlockIndexer() + { + $this->_lockFlag = false; + return $this; + } + + /** + * Check if onject actions are locked + * + * @return bool + */ + public function isLocked() + { + return $this->_lockFlag; + } + + /** + * Indexing all pending events. + * Events set can be limited by event entity and type + * + * @param null | string $entity + * @param null | string $type + * @return Mage_Index_Model_Indexer + */ + public function indexEvents($entity=null, $type=null) + { + if ($this->isLocked()) { + return $this; + } + + foreach ($this->_processesCollection as $process) { + $process->indexEvents($entity, $type); + } + return $this; + } + + /** + * Index one event by all processes + * + * @param Mage_Index_Model_Event $event + * @return Mage_Index_Model_Indexer + */ + public function indexEvent(Mage_Index_Model_Event $event) + { + if ($this->isLocked()) { + return $this; + } + + foreach ($this->_processesCollection as $process) { + $process->processEvent($event); + } + return $this; + } + + /** + * Register event in each indexing process process + * + * @param Mage_Index_Model_Event $event + */ + public function registerEvent(Mage_Index_Model_Event $event) + { + if ($this->isLocked()) { + return $this; + } + + foreach ($this->_processesCollection as $process) { + $process->register($event); + } + return $this; + } + + /** + * Create new event log and register event in all processes + * + * @param Varien_Object $entity + * @param string $entityType + * @param string $eventType + * @param bool $doSave + * @return Mage_Index_Model_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) + ->setDataObject($entity) + ->setEntityPk($entity->getId()); + + $this->registerEvent($event); + if ($doSave) { + $event->save(); + } + return $event; + } + + /** + * Create new event log and register event in all processes. + * Initiate events indexing procedure. + * + * @param Varien_Object $entity + * @param string $entityType + * @param string $eventType + * @return Mage_Index_Model_Indexer + */ + 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 + */ + if ($event->getProcessIds()) { + $this->indexEvent($event); + $event->save(); + } + return $this; + } +} diff --git a/app/code/core/Mage/Index/Model/Indexer/Abstract.php b/app/code/core/Mage/Index/Model/Indexer/Abstract.php new file mode 100644 index 0000000000..15f81fd343 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Indexer/Abstract.php @@ -0,0 +1,148 @@ +matchEvent($event)) { + $this->_registerEvent($event); + } + return $this; + } + + /** + * Process event + * + * @param Mage_Index_Model_Event $event + * @return Mage_Index_Model_Indexer_Abstract + */ + public function processEvent(Mage_Index_Model_Event $event) + { + if ($this->matchEvent($event)) { + $this->_processEvent($event); + } + return $this; + } + + /** + * Check if event can be matched by process + * + * @param Mage_Index_Model_Event $event + * @return bool + */ + public function matchEvent(Mage_Index_Model_Event $event) + { + $entity = $event->getEntity(); + $type = $event->getType(); + return $this->matchEntityAndType($entity, $type); + } + + /** + * Check if indexer matched specific entity and action type + * + * @param string $entity + * @param string $type + * @return bool + */ + public function matchEntityAndType($entity, $type) + { + if (isset($this->_matchedEntities[$entity])) { + if (in_array($type, $this->_matchedEntities[$entity])) { + return true; + } + } + return false; + } + + /** + * Rebuild all index data + */ + public function reindexAll() + { + $this->_getResource()->reindexAll(); + } + + /** + * Try dynamicly detect and call event hanler from resource model. + * Handler name will be generated from event entity and type code + * + * @param Mage_Index_Model_Event $event + * @return Mage_Index_Model_Indexer_Abstract + */ + public function callEventHandler(Mage_Index_Model_Event $event) + { + if ($event->getEntity()) { + $method = $this->_camelize($event->getEntity().'_'.$event->getType()); + } else { + $method = $this->_camelize($event->getType()); + } + + if (method_exists($this->_getResource(), $method)) { + $this->_getResource()->$method($event); + } + return $this; + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Index/Model/Mysql4/Abstract.php b/app/code/core/Mage/Index/Model/Mysql4/Abstract.php new file mode 100644 index 0000000000..9915052de4 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Mysql4/Abstract.php @@ -0,0 +1,167 @@ +_getWriteAdapter(); + } + + /** + * Get index table name with additional suffix + * + * @param string $table + * @return string + */ + public function getIdxTable($table = null) + { + if ($table) { + return $table . self::IDX_SUFFIX; + } + return $this->getMainTable() . self::IDX_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; + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Index/Model/Mysql4/Event.php b/app/code/core/Mage/Index/Model/Mysql4/Event.php new file mode 100644 index 0000000000..ec2fcd2468 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Mysql4/Event.php @@ -0,0 +1,87 @@ +_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); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Index/Model/Mysql4/Event/Collection.php b/app/code/core/Mage/Index/Model/Mysql4/Event/Collection.php new file mode 100644 index 0000000000..43d87f9fa8 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Mysql4/Event/Collection.php @@ -0,0 +1,122 @@ +_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; + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Index/Model/Mysql4/Process.php b/app/code/core/Mage/Index/Model/Mysql4/Process.php new file mode 100644 index 0000000000..c6ca6bd6a8 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Mysql4/Process.php @@ -0,0 +1,110 @@ +_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; + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Index/Model/Mysql4/Process/Collection.php b/app/code/core/Mage/Index/Model/Mysql4/Process/Collection.php new file mode 100644 index 0000000000..5bd4796f3d --- /dev/null +++ b/app/code/core/Mage/Index/Model/Mysql4/Process/Collection.php @@ -0,0 +1,32 @@ +_init('index/process'); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Index/Model/Mysql4/Setup.php b/app/code/core/Mage/Index/Model/Mysql4/Setup.php new file mode 100644 index 0000000000..cfe1f3a463 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Mysql4/Setup.php @@ -0,0 +1,61 @@ +_syncIndexes(); + } + + /** + * Sync indexes declarations in config and in DB + */ + protected function _syncIndexes() + { + $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'); + $connection = $this->getConnection(); + $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)) { + $connection->insertArray($table, array('indexer_code'), $insert); + } + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Index/Model/Observer.php b/app/code/core/Mage/Index/Model/Observer.php new file mode 100644 index 0000000000..8f08cb3116 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Observer.php @@ -0,0 +1,146 @@ +_indexer = Mage::getModel('index/indexer'); + } + + /** + * Store after commit observer. Process store related indexes + * + * @param Varien_Event_Observer $observer + */ + public function processStoreSave(Varien_Event_Observer $observer) + { + $store = $observer->getEvent()->getStore(); + $this->_indexer->processEntityAction( + $store, + Mage_Core_Model_Store::ENTITY, + Mage_Index_Model_Event::TYPE_SAVE + ); + } + + /** + * Store group after commit observer. Process store group related indexes + * + * @param Varien_Event_Observer $observer + */ + public function processStoreGroupSave(Varien_Event_Observer $observer) + { + $storeGroup = $observer->getEvent()->getStoreGroup(); + $this->_indexer->processEntityAction( + $storeGroup, + Mage_Core_Model_Store_Group::ENTITY, + Mage_Index_Model_Event::TYPE_SAVE + ); + } + + /** + * Website save after commit observer. Process website related indexes + * + * @param Varien_Event_Observer $observer + */ + public function processWebsiteSave(Varien_Event_Observer $observer) + { + $website = $observer->getEvent()->getWebsite(); + $this->_indexer->processEntityAction( + $website, + Mage_Core_Model_Website::ENTITY, + Mage_Index_Model_Event::TYPE_SAVE + ); + } + + /** + * Store after commit observer. Process store related indexes + * + * @param Varien_Event_Observer $observer + */ + public function processStoreDelete(Varien_Event_Observer $observer) + { + $store = $observer->getEvent()->getStore(); + $this->_indexer->processEntityAction( + $store, + Mage_Core_Model_Store::ENTITY, + Mage_Index_Model_Event::TYPE_DELETE + ); + } + + /** + * Store group after commit observer. Process store group related indexes + * + * @param Varien_Event_Observer $observer + */ + public function processStoreGroupDelete(Varien_Event_Observer $observer) + { + $storeGroup = $observer->getEvent()->getStoreGroup(); + $this->_indexer->processEntityAction( + $storeGroup, + Mage_Core_Model_Store_Group::ENTITY, + Mage_Index_Model_Event::TYPE_DELETE + ); + } + + /** + * Website save after commit observer. Process website related indexes + * + * @param Varien_Event_Observer $observer + */ + public function processWebsiteDelete(Varien_Event_Observer $observer) + { + $website = $observer->getEvent()->getWebsite(); + $this->_indexer->processEntityAction( + $website, + Mage_Core_Model_Website::ENTITY, + Mage_Index_Model_Event::TYPE_DELETE + ); + } + + /** + * Config data after commit observer. + * + * @param Varien_Event_Observer $observer + */ + public function processConfigDataSave(Varien_Event_Observer $observer) + { + $configData = $observer->getEvent()->getConfigData(); + $this->_indexer->processEntityAction( + $configData, + Mage_Core_Model_Config_Data::ENTITY, + Mage_Index_Model_Event::TYPE_SAVE + ); + } + +} \ No newline at end of file diff --git a/app/code/core/Mage/Index/Model/Process.php b/app/code/core/Mage/Index/Model/Process.php new file mode 100644 index 0000000000..e36281d1e5 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Process.php @@ -0,0 +1,390 @@ +_init('index/process'); + } + + /** + * Set indexer class name as data namespace for event object + * + * @param Mage_Index_Model_Event $event + * @return Mage_Index_Model_Process + */ + protected function _setEventNamespace(Mage_Index_Model_Event $event) + { + $namespace = get_class($this->getIndexer()); + $event->setDataNamespace($namespace); + $event->setProcess($this); + return $this; + } + + /** + * Remove indexer namespace from event + * + * @return Mage_Index_Model_Process + */ + protected function _resetEventNamespace($event) + { + $event->setDataNamespace(null); + $event->setProcess(null); + return $this; + } + + /** + * Register data required by process in event object + * + * @param Mage_Index_Model_Event $event + */ + public function register(Mage_Index_Model_Event $event) + { + if ($this->matchEvent($event)) { + $this->_setEventNamespace($event); + $this->getIndexer()->register($event); + $event->addProcessId($this->getId()); + $this->_resetEventNamespace($event); + } + return $this; + + } + + /** + * Check if event can be matched by process + * + * @param Mage_Index_Model_Event $event + * @return bool + */ + public function matchEvent(Mage_Index_Model_Event $event) + { + return $this->getIndexer()->matchEvent($event); + } + + /** + * Reindex all data what this process responsible is + * + * @return unknown_type + */ + public function reindexAll() + { + if ($this->isLocked()) { + Mage::throwException(Mage::helper('index')->__('%s Index proces is working now. Please try run this process later.', $this->getIndexer()->getName())); + } + $this->_getResource()->startProcess($this); + $this->lock(); + $this->getIndexer()->reindexAll(); + $this->unlock(); + $this->_getResource()->endProcess($this); + } + + /** + * Process event with assigned indexer object + * + * @param Mage_Index_Model_Event $event + * @return Mage_Index_Model_Process + */ + public function processEvent(Mage_Index_Model_Event $event) + { + if ($this->getMode() == self::MODE_MANUAL) { + return $this; + } + if (!$this->getIndexer()->matchEvent($event)) { + return $this; + } + $this->_setEventNamespace($event); + $this->getIndexer()->processEvent($event); + $event->resetData(); + $this->_resetEventNamespace($event); + $event->addProcessId($this->getId(), self::EVENT_STATUS_DONE); + return $this; + } + + /** + * Get Indexer strategy object + * + * @return Mage_Index_Model_Indexer_Abstract + */ + public function getIndexer() + { + if ($this->_indexer === null) { + $code = $this->_getData('indexer_code'); + if (!$code) { + Mage::throwException(Mage::helper('index')->__('Indexer code is not defined.')); + } + $xmlPath = self::XML_PATH_INDEXER_DATA . '/' . $code; + $config = Mage::getConfig()->getNode($xmlPath); + if (!$config || empty($config->model)) { + Mage::throwException(Mage::helper('index')->__('Indexer model is not defined.')); + } + $model = Mage::getModel((string)$config->model); + if ($model instanceof Mage_Index_Model_Indexer_Abstract) { + $this->_indexer = $model; + } else { + Mage::throwException(Mage::helper('index')->__('Indexer model should extend Mage_Index_Model_Indexer_Abstract.')); + } + } + return $this->_indexer; + } + + /** + * Index pending events addressed to the process + * + * @param null|string $entity + * @param null|string $type + * @return Mage_Index_Model_Process + */ + public function indexEvents($entity=null, $type=null) + { + /** + * Check if process indexer can match entity code and action type + */ + if ($entity !== null && $type !== null) { + if (!$this->getIndexer()->matchEntityAndType($entity, $type)) { + return $this; + } + } + + if ($this->getMode() == self::MODE_MANUAL) { + return $this; + } + + if ($this->isLocked()) { + return $this; + } + $this->lock(); + + /** + * 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); + } + + /** + * 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); + } + $event->save(); + } + $eventsCollection->reset(); + } + + $this->unlock(); + return $this; + } + + /** + * Update status process/event association + * + * @param Mage_Index_Model_Event $event + * @param string $status + * @return Mage_Index_Model_Process + */ + public function updateEventStatus(Mage_Index_Model_Event $event, $status) + { + $this->_getResource()->updateEventStatus($this->getId(), $event->getId(), $status); + return $this; + } + + /** + * Get lock file resource + * + * @return resource + */ + protected function _getLockFile() + { + if ($this->_lockFile === null) { + $varDir = Mage::getConfig()->getVarDir('locks'); + $file = $varDir . DS . 'index_process_'.$this->getId().'.lock'; + if (is_file($file)) { + $this->_lockFile = fopen($file, 'w'); + } else { + $this->_lockFile = fopen($file, 'x'); + } + fwrite($this->_lockFile, date('r')); + } + return $this->_lockFile; + } + + /** + * Lock process without blocking. + * This method allow protect multiple process runing and fast lock validation. + * + * @return Mage_Index_Model_Process + */ + public function lock() + { + $this->_isLocked = true; + flock($this->_getLockFile(), LOCK_EX | LOCK_NB); + return $this; + } + + /** + * Lock and block process. + * If new instance of the process will try validate locking state + * script will wait until process will be unlocked + * + * @return Mage_Index_Model_Process + */ + public function lockAndBlock() + { + $this->_isLocked = true; + flock($this->_getLockFile(), LOCK_EX); + return $this; + } + + /** + * Unlock process + * + * @return Mage_Index_Model_Process + */ + public function unlock() + { + $this->_isLocked = false; + flock($this->_getLockFile(), LOCK_UN); + return $this; + } + + /** + * Check if process is locked + * + * @return bool + */ + public function isLocked() + { + if ($this->_isLocked !== null) { + return $this->_isLocked; + } else { + $fp = $this->_getLockFile(); + if (flock($fp, LOCK_EX | LOCK_NB)) { + flock($fp, LOCK_UN); + return false; + } + return true; + } + } + + /** + * Close file resource if it was opened + */ + public function __destruct() + { + if ($this->_lockFile) { + fclose($this->_lockFile); + } + } + + /** + * Change process status + * + * @param string $status + * @return Mage_Index_Model_Process + */ + public function changeStatus($status) + { + $this->_getResource()->updateStatus($this, $status); + return $this; + } + + /** + * Get list of process mode options + * + * @return array + */ + public function getModesOptions() + { + return array( + self::MODE_REAL_TIME => Mage::helper('index')->__('Update on Save'), + self::MODE_MANUAL => Mage::helper('index')->__('Manual Update') + ); + } + + /** + * Get list of process status options + * + * @return array + */ + public function getStatusesOptions() + { + return array( + self::STATUS_PENDING => Mage::helper('index')->__('Ready'), + self::STATUS_RUNNING => Mage::helper('index')->__('Processing'), + self::STATUS_REQUIRE_REINDEX => Mage::helper('index')->__('Reindex Required'), + ); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Index/controllers/Adminhtml/ProcessController.php b/app/code/core/Mage/Index/controllers/Adminhtml/ProcessController.php new file mode 100644 index 0000000000..1e579fa8de --- /dev/null +++ b/app/code/core/Mage/Index/controllers/Adminhtml/ProcessController.php @@ -0,0 +1,217 @@ +getRequest()->getParam('process'); + if ($processId) { + $process = Mage::getModel('index/process')->load($processId); + if ($process->getId()) { + return $process; + } + } + return false; + } + + /** + * Display processes grid action + */ + public function listAction() + { + $this->loadLayout(); + $this->_setActiveMenu('system/index'); + $this->_addContent($this->getLayout()->createBlock('index/adminhtml_process')); + $this->renderLayout(); + } + + /** + * Process detail and edit action + */ + public function editAction() + { + $process = $this->_initProcess(); + if ($process) { + Mage::register('current_index_process', $process); + $this->loadLayout(); + $this->renderLayout(); + } else { + $this->_getSession()->addError( + Mage::helper('index')->__('Can\'t initialize indexer process.') + ); + $this->_redirect('*/*/list'); + } + } + + /** + * Save process data + */ + public function saveAction() + { + $process = $this->_initProcess(); + if ($process) { + $mode = $this->getRequest()->getPost('mode'); + if ($mode) { + $process->setMode($mode); + } + try { + $process->save(); + $this->_getSession()->addSuccess( + Mage::helper('index')->__('Index was saved successfully.') + ); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } catch (Exception $e) { + $this->_getSession()->addException($e, + Mage::helper('index')->__('Some problem with saving process.') + ); + } + $this->_redirect('*/*/list'); + } else { + $this->_getSession()->addError( + Mage::helper('index')->__('Can\'t initialize indexer process.') + ); + $this->_redirect('*/*/list'); + } + } + + /** + * Reindex all data what process is responsible + */ + public function reindexProcessAction() + { + $process = $this->_initProcess(); + if ($process) { + try { + Varien_Profiler::start('__INDEX_PROCESS_REINDEX_ALL__'); + $process->reindexAll(); + Varien_Profiler::stop('__INDEX_PROCESS_REINDEX_ALL__'); + $this->_getSession()->addSuccess( + Mage::helper('index')->__('%s index was rebuilt successfully.', $process->getIndexer()->getName()) + ); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } catch (Exception $e) { + $this->_getSession()->addException($e, + Mage::helper('index')->__('Some problem with reindexing process.') + ); + } + } else { + $this->_getSession()->addError( + Mage::helper('index')->__('Can\'t initialize indexer process.') + ); + } + + $this->_redirect('*/*/list'); + } + + /** + * Reindex pending events for index process + */ + public function reindexEventsAction() + { + + } + + /** + * Rebiuld all processes index + */ + public function reindexAllAction() + { + + } + + /** + * Mass rebuild selected processes index + * + */ + public function massReindexAction() + { + $processIds = $this->getRequest()->getParam('process'); + if (empty($processIds) || !is_array($processIds)) { + $this->_getSession()->addError(Mage::helper('index')->__('Please select Indexes')); + } else { + try { + foreach ($processIds as $processId) { + /* @var $process Mage_Index_Model_Process */ + $process = Mage::getModel('index/process')->load($processId); + if ($process->getId()) { + $process->reindexAll(); + } + } + $count = count($processIds); + $this->_getSession()->addSuccess( + Mage::helper('index')->__('Total of %d index(es) have successfully reindexed data', $count) + ); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } catch (Exception $e) { + $this->_getSession()->addException($e, Mage::helper('index')->__('Can\'t initialize indexer process.')); + } + } + + $this->_redirect('*/*/list'); + } + + /** + * Mass change index mode of selected processes index + * + */ + public function massChangeModeAction() + { + $processIds = $this->getRequest()->getParam('process'); + if (empty($processIds) || !is_array($processIds)) { + $this->_getSession()->addError(Mage::helper('index')->__('Please select Index(es)')); + } else { + try { + $mode = $this->getRequest()->getParam('index_mode'); + foreach ($processIds as $processId) { + /* @var $process Mage_Index_Model_Process */ + $process = Mage::getModel('index/process')->load($processId); + if ($process->getId()) { + $process->setMode($mode) + ->save(); + } + } + $count = count($processIds); + $this->_getSession()->addSuccess( + Mage::helper('index')->__('Total of %d index(es) were successfully changed index mode', $count) + ); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } catch (Exception $e) { + $this->_getSession()->addException($e, Mage::helper('index')->__('Can\'t initialize indexer process.')); + } + } + + $this->_redirect('*/*/list'); + } +} diff --git a/app/code/core/Mage/Index/etc/adminhtml.xml b/app/code/core/Mage/Index/etc/adminhtml.xml new file mode 100644 index 0000000000..e75fb9d619 --- /dev/null +++ b/app/code/core/Mage/Index/etc/adminhtml.xml @@ -0,0 +1,55 @@ + + + + + + + + Index Management + adminhtml/process/list + 92 + + + + + + + + + + + + Index Management + + + + + + + + \ No newline at end of file diff --git a/app/code/core/Mage/Index/etc/config.xml b/app/code/core/Mage/Index/etc/config.xml new file mode 100644 index 0000000000..b80c15ab48 --- /dev/null +++ b/app/code/core/Mage/Index/etc/config.xml @@ -0,0 +1,145 @@ + + + + + + 1.4.0.2 + + + + + + Mage_Index_Model + index_mysql4 + + + Mage_Index_Model_Mysql4 + + index_event + index_process + index_process_event + + + + + + + Mage_Index + Mage_Index_Model_Mysql4_Setup + + + + + + + + + + + + + index/observer + processStoreSave + + + + + + + index/observer + processStoreGroupSave + + + + + + + index/observer + processWebsiteSave + + + + + + + index/observer + processStoreDelete + + + + + + + index/observer + processStoreGroupDelete + + + + + + + index/observer + processWebsiteDelete + + + + + + + index/observer + processConfigDataSave + + + + + + + + + + + Mage_Index_Adminhtml + + + + + + + + + + index.xml + + + + + \ No newline at end of file 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 new file mode 100644 index 0000000000..7388bddaa4 --- /dev/null +++ b/app/code/core/Mage/Index/sql/index_setup/mysql4-install-1.4.0.0.php @@ -0,0 +1,63 @@ +startSetup(); +$installer->run(" +CREATE TABLE `{$this->getTable('index/event')}` ( + `event_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `type` varchar(64) NOT NULL, + `entity` varchar(64) NOT NULL, + `entity_pk` bigint(20) DEFAULT NULL, + `created_at` datetime NOT NULL, + `old_data` mediumtext, + `new_data` mediumtext, + PRIMARY KEY (`event_id`), + UNIQUE KEY `IDX_UNIQUE_EVENT` (`type`,`entity`,`entity_pk`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `{$this->getTable('index/process')}` ( + `process_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `indexer_code` varchar(32) NOT NULL, + `status` enum('pending','working') NOT NULL DEFAULT 'pending', + `started_at` datetime DEFAULT NULL, + `ended_at` datetime DEFAULT NULL, + PRIMARY KEY (`process_id`), + UNIQUE KEY `IDX_CODE` (`indexer_code`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `{$this->getTable('index/process_event')}` ( + `process_id` int(10) unsigned NOT NULL, + `event_id` bigint(20) unsigned NOT NULL, + `status` enum('new','working','done','error') NOT NULL DEFAULT 'new', + PRIMARY KEY (`process_id`,`event_id`), + KEY `FK_INDEX_EVNT_PROCESS` (`event_id`), + CONSTRAINT `FK_INDEX_EVNT_PROCESS` FOREIGN KEY (`event_id`) REFERENCES `{$this->getTable('index/event')}` (`event_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_INDEX_PROCESS_EVENT` FOREIGN KEY (`process_id`) REFERENCES `{$this->getTable('index/process')}` (`process_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +"); +$installer->endSetup(); \ No newline at end of file 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 new file mode 100644 index 0000000000..bfd2f75493 --- /dev/null +++ b/app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.4.0.0-1.4.0.1.php @@ -0,0 +1,33 @@ +getConnection()->addColumn( + $this->getTable('index/process'), + 'mode', + "enum('real_time','manual') DEFAULT 'real_time' NOT NULL after `ended_at`" +); 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 new file mode 100644 index 0000000000..1256f53afb --- /dev/null +++ b/app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.4.0.1-1.4.0.2.php @@ -0,0 +1,34 @@ +getConnection()->changeColumn( + $this->getTable('index/process'), + 'status', + 'status', + "enum('pending','working','require_reindex') DEFAULT 'pending' NOT NULL" +); diff --git a/app/code/core/Mage/Install/etc/config.xml b/app/code/core/Mage/Install/etc/config.xml index d99d1ea67d..ccdb6a2adb 100644 --- a/app/code/core/Mage/Install/etc/config.xml +++ b/app/code/core/Mage/Install/etc/config.xml @@ -41,14 +41,6 @@ Mage_Install_Model_Mysql4 - - - core_write - - - core_read - - Mage_Install_Block @@ -77,7 +69,6 @@ - singleton core/resource checkDbConnection @@ -86,7 +77,6 @@ - singleton install/observer installFailure @@ -95,7 +85,6 @@ - singleton install/observer bindLocale diff --git a/app/code/core/Mage/Log/Model/Mysql4/Log.php b/app/code/core/Mage/Log/Model/Mysql4/Log.php index b6e9f05918..db5af20266 100644 --- a/app/code/core/Mage/Log/Model/Mysql4/Log.php +++ b/app/code/core/Mage/Log/Model/Mysql4/Log.php @@ -43,6 +43,36 @@ protected function _construct() $this->_init('log/visitor', 'visitor_id'); } + /** + * Start resource transaction + * + * @return Mage_Log_Model_Mysql4_Log + */ + public function beginTransaction() + { + return $this; + } + + /** + * Commit resource transaction + * + * @return Mage_Log_Model_Mysql4_Log + */ + public function commit() + { + return $this; + } + + /** + * Roll back resource transaction + * + * @return Mage_Log_Model_Mysql4_Log + */ + public function rollBack() + { + return $this; + } + /** * Clean logs * diff --git a/app/code/core/Mage/Log/Model/Mysql4/Visitor.php b/app/code/core/Mage/Log/Model/Mysql4/Visitor.php index 1958805444..5e375ee625 100644 --- a/app/code/core/Mage/Log/Model/Mysql4/Visitor.php +++ b/app/code/core/Mage/Log/Model/Mysql4/Visitor.php @@ -38,6 +38,36 @@ protected function _construct() $this->_init('log/visitor', 'visitor_id'); } + /** + * Start resource transaction + * + * @return Mage_Log_Model_Mysql4_Visitor + */ + public function beginTransaction() + { + return $this; + } + + /** + * Commit resource transaction + * + * @return Mage_Log_Model_Mysql4_Visitor + */ + public function commit() + { + return $this; + } + + /** + * Roll back resource transaction + * + * @return Mage_Log_Model_Mysql4_Visitor + */ + public function rollBack() + { + return $this; + } + protected function _prepareDataForSave(Mage_Core_Model_Abstract $visitor) { return array( diff --git a/app/code/core/Mage/Log/Model/Visitor.php b/app/code/core/Mage/Log/Model/Visitor.php index d849cc97a6..7dc94b7b74 100644 --- a/app/code/core/Mage/Log/Model/Visitor.php +++ b/app/code/core/Mage/Log/Model/Visitor.php @@ -175,10 +175,15 @@ public function saveByRequest($observer) return $this; } - $this->setLastVisitAt(now()); - $this->save(); + try { + $this->setLastVisitAt(now()); + $this->save(); - $this->_getSession()->setVisitorData($this->getData()); + $this->_getSession()->setVisitorData($this->getData()); + } + catch (Exception $e) { + Mage::logException($e); + } return $this; } diff --git a/app/code/core/Mage/Log/etc/config.xml b/app/code/core/Mage/Log/etc/config.xml index 9558efdd30..752e58c0f4 100644 --- a/app/code/core/Mage/Log/etc/config.xml +++ b/app/code/core/Mage/Log/etc/config.xml @@ -82,20 +82,7 @@ Mage_Log - - core_setup - - - - core_write - - - - - core_read - - @@ -112,7 +99,6 @@ - singleton log/visitor initByRequest @@ -121,7 +107,6 @@ - singleton log/visitor saveByRequest @@ -130,7 +115,6 @@ - singleton log/visitor bindCustomerLogin @@ -139,7 +123,6 @@ - singleton log/visitor bindCustomerLogout @@ -148,7 +131,6 @@ - singleton log/visitor bindQuoteCreate @@ -157,7 +139,6 @@ - singleton log/visitor bindQuoteDestroy diff --git a/app/code/core/Mage/Newsletter/Block/Subscribe.php b/app/code/core/Mage/Newsletter/Block/Subscribe.php index 5f572f58f4..656eed37a4 100644 --- a/app/code/core/Mage/Newsletter/Block/Subscribe.php +++ b/app/code/core/Mage/Newsletter/Block/Subscribe.php @@ -45,4 +45,15 @@ public function getErrorMessage() $message = Mage::getSingleton('newsletter/session')->getError(); return $message; } -} \ No newline at end of file + + /** + * Retrieve form action url and set "secure" param to avoid confirm + * message when we submit form from secure page to unsecure + * + * @return string + */ + public function getFormActionUrl() + { + return $this->getUrl('newsletter/subscriber/new', array('_secure' => true)); + } +} diff --git a/app/code/core/Mage/Newsletter/Model/Mysql4/Queue/Collection.php b/app/code/core/Mage/Newsletter/Model/Mysql4/Queue/Collection.php index 63ed4bb8b8..cf712eba17 100644 --- a/app/code/core/Mage/Newsletter/Model/Mysql4/Queue/Collection.php +++ b/app/code/core/Mage/Newsletter/Model/Mysql4/Queue/Collection.php @@ -34,17 +34,18 @@ class Mage_Newsletter_Model_Mysql4_Queue_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract { - protected $_addSubscribersFlag = false; + protected $_addSubscribersFlag = false; /** * @var bool */ protected $_isStoreFilter = false; - /** - * Initializes collection - */ + /** + * Initializes collection + */ protected function _construct() { + $this->_map['fields']['queue_id'] = 'main_table.queue_id'; $this->_init('newsletter/queue'); } @@ -58,35 +59,35 @@ public function addTemplateInfo() { $this->getSelect()->joinLeft(array('template'=>$this->getTable('template')), 'template.template_id=main_table.template_id', array('template_subject','template_sender_name','template_sender_email')); - $this->_joinedTables['template'] = true; - return $this; + $this->_joinedTables['template'] = true; + return $this; } protected function _addSubscriberInfoToSelect() { $this->_addSubscribersFlag = true; - $this->getSize(); // Executing of count query! - $this->getSelect() - ->joinLeft(array('link_total'=>$this->getTable('queue_link')), - 'main_table.queue_id=link_total.queue_id', - array( - new Zend_Db_Expr('COUNT(DISTINCT link_total.queue_link_id) AS subscribers_total') - )) - ->joinLeft(array('link_sent'=>$this->getTable('queue_link')), - 'main_table.queue_id=link_sent.queue_id and link_sent.letter_sent_at IS NOT NULL', - array( - new Zend_Db_Expr('COUNT(DISTINCT link_sent.queue_link_id) AS subscribers_sent') - )) - ->group('main_table.queue_id'); + $this->getSize(); // Executing of count query! + $select = $this->getConnection() + ->select() + ->from(array('link_total' => $this->getTable('queue_link')), 'COUNT(DISTINCT `link_total`.`queue_link_id`)') + ->where('`main_table`.`queue_id` = `link_total`.`queue_id`'); + $this->getSelect() + ->joinLeft(array('link_sent'=>$this->getTable('queue_link')), + 'main_table.queue_id=link_sent.queue_id and link_sent.letter_sent_at IS NOT NULL', + array( + new Zend_Db_Expr('COUNT(DISTINCT `link_sent`.`queue_link_id`) AS `subscribers_sent`'), + new Zend_Db_Expr('(' . $select . ') AS `subscribers_total`') + )) + ->group('main_table.queue_id'); return $this; } - public function load($printQuery=false, $logQuery=false) { - if($this->_addSubscribersFlag && !$this->isLoaded()) { - $this->_addSubscriberInfoToSelect(); - } - - return parent::load($printQuery, $logQuery); + public function load($printQuery=false, $logQuery=false) + { + if($this->_addSubscribersFlag && !$this->isLoaded()) { + $this->_addSubscriberInfoToSelect(); + } + return parent::load($printQuery, $logQuery); } /** @@ -96,55 +97,55 @@ public function load($printQuery=false, $logQuery=false) { */ public function addSubscribersInfo() { - $this->_addSubscribersFlag = true; - return $this; + $this->_addSubscribersFlag = true; + return $this; } public function addFieldToFilter($field, $condition=null) { - if(in_array($field, array('subscribers_total', 'subscribers_sent'))) { - $this->addFieldToFilter('main_table.queue_id', array('in'=>$this->_getIdsFromLink($field, $condition))); - return $this; - } else { - return parent::addFieldToFilter($field, $condition); - } + if(in_array($field, array('subscribers_total', 'subscribers_sent'))) { + $this->addFieldToFilter('main_table.queue_id', array('in'=>$this->_getIdsFromLink($field, $condition))); + return $this; + } else { + return parent::addFieldToFilter($field, $condition); + } } protected function _getIdsFromLink($field, $condition) { - $select = $this->getConnection()->select() - ->from($this->getTable('queue_link'), array('queue_id', 'COUNT(queue_link_id) as total')) - ->group('queue_id') - ->having($this->_getConditionSql('total', $condition)); + $select = $this->getConnection()->select() + ->from($this->getTable('queue_link'), array('queue_id', 'COUNT(queue_link_id) as total')) + ->group('queue_id') + ->having($this->_getConditionSql('total', $condition)); - if($field == 'subscribers_sent') { - $select->where('letter_sent_at IS NOT NULL'); - } + if($field == 'subscribers_sent') { + $select->where('letter_sent_at IS NOT NULL'); + } - $idList = $this->getConnection()->fetchCol($select); + $idList = $this->getConnection()->fetchCol($select); - if(count($idList)) { - return $idList; - } + if(count($idList)) { + return $idList; + } - return array(0); + return array(0); } /** * Set filter for queue by subscriber. * - * @param int $subscriberId - * @return Mage_Newsletter_Model_Mysql4_Queue_Collection + * @param int $subscriberId + * @return Mage_Newsletter_Model_Mysql4_Queue_Collection */ public function addSubscriberFilter($subscriberId) { - $this->getSelect() - ->join(array('link'=>$this->getTable('queue_link')), - 'main_table.queue_id=link.queue_id', - array('letter_sent_at') - ) - ->where('link.subscriber_id = ?', $subscriberId); - - return $this; + $this->getSelect() + ->join(array('link'=>$this->getTable('queue_link')), + 'main_table.queue_id=link.queue_id', + array('letter_sent_at') + ) + ->where('link.subscriber_id = ?', $subscriberId); + + return $this; } /** @@ -154,13 +155,13 @@ public function addSubscriberFilter($subscriberId) */ public function addOnlyForSendingFilter() { - $this->getSelect() - ->where('main_table.queue_status in (?)', array(Mage_Newsletter_Model_Queue::STATUS_SENDING, - Mage_Newsletter_Model_Queue::STATUS_NEVER)) - ->where('main_table.queue_start_at < ?', Mage::getSingleton('core/date')->gmtdate()) - ->where('main_table.queue_start_at IS NOT NULL'); + $this->getSelect() + ->where('main_table.queue_status in (?)', array(Mage_Newsletter_Model_Queue::STATUS_SENDING, + Mage_Newsletter_Model_Queue::STATUS_NEVER)) + ->where('main_table.queue_start_at < ?', Mage::getSingleton('core/date')->gmtdate()) + ->where('main_table.queue_start_at IS NOT NULL'); - return $this; + return $this; } /** @@ -170,10 +171,10 @@ public function addOnlyForSendingFilter() */ public function addOnlyUnsentFilter() { - $this->getSelect() - ->where('main_table.queue_status = ?', Mage_Newsletter_Model_Queue::STATUS_NEVER); + $this->getSelect() + ->where('main_table.queue_status = ?', Mage_Newsletter_Model_Queue::STATUS_NEVER); - return $this; + return $this; } public function toOptionArray() diff --git a/app/code/core/Mage/Newsletter/Model/Subscriber.php b/app/code/core/Mage/Newsletter/Model/Subscriber.php index 798230c1a2..83c1191919 100644 --- a/app/code/core/Mage/Newsletter/Model/Subscriber.php +++ b/app/code/core/Mage/Newsletter/Model/Subscriber.php @@ -45,7 +45,7 @@ class Mage_Newsletter_Model_Subscriber extends Varien_Object const XML_PATH_UNSUBSCRIBE_EMAIL_IDENTITY = 'newsletter/subscription/un_email_identity'; const XML_PATH_CONFIRMATION_FLAG = 'newsletter/subscription/confirm'; - const XML_PATH_SENDING_SET_RETURN_PATH = 'newsletter/sending/set_return_path'; + const XML_PATH_SENDING_SET_RETURN_PATH = Mage_Core_Model_Email_Template::XML_PATH_SENDING_SET_RETURN_PATH; protected $_isStatusChanged = false; @@ -282,7 +282,6 @@ public function subscribe($email) $customer = Mage::getModel('customer/customer') ->setWebsiteId(Mage::app()->getStore()->getWebsiteId()) ->loadByEmail($email); - $isNewSubscriber = false; $customerSession = Mage::getSingleton('customer/session'); @@ -290,15 +289,10 @@ public function subscribe($email) $this->setSubscriberConfirmCode($this->randomSequence()); } -// if(($this->getCustomerId() && !$customerSession->isLoggedIn()) -// || ($this->getCustomerId() -// && $customerSession->getCustomerId() != $this->getCustomerId() -// )) { -// return $this->getSubscriberStatus(); -// } + $isConfirmNeed = Mage::getStoreConfig(self::XML_PATH_CONFIRMATION_FLAG) == 1 ? true : false; if (!$this->getId() || $this->getStatus()==self::STATUS_UNSUBSCRIBED || $this->getStatus()==self::STATUS_NOT_ACTIVE) { - if (Mage::getStoreConfig(self::XML_PATH_CONFIRMATION_FLAG) == 1) { + if ($isConfirmNeed) { $this->setStatus(self::STATUS_NOT_ACTIVE); } else { $this->setStatus(self::STATUS_SUBSCRIBED); @@ -317,16 +311,14 @@ public function subscribe($email) } else { $this->setStoreId(Mage::app()->getStore()->getId()); $this->setCustomerId(0); - $isNewSubscriber = true; } $this->setIsStatusChanged(true); try { $this->save(); - if (Mage::getStoreConfig(self::XML_PATH_CONFIRMATION_FLAG) == 1 - && $this->getSubscriberStatus()==self::STATUS_NOT_ACTIVE) { - $this->sendConfirmationRequestEmail(); + if ($isConfirmNeed) { + $this->sendConfirmationRequestEmail(); } else { $this->sendConfirmationSuccessEmail(); } @@ -453,10 +445,7 @@ public function sendConfirmationRequestEmail() $translate->setTranslateInline(false); $email = Mage::getModel('core/email_template'); - /* @var $email Mage_Core_Model_Email_Template */ - if (Mage::getStoreConfigFlag(self::XML_PATH_SENDING_SET_RETURN_PATH)) { - $email->setReturnPath(Mage::getStoreConfig(self::XML_PATH_CONFIRM_EMAIL_IDENTITY)); - } + $email->sendTransactional( Mage::getStoreConfig(self::XML_PATH_CONFIRM_EMAIL_TEMPLATE), Mage::getStoreConfig(self::XML_PATH_CONFIRM_EMAIL_IDENTITY), @@ -485,10 +474,7 @@ public function sendConfirmationSuccessEmail() $translate->setTranslateInline(false); $email = Mage::getModel('core/email_template'); - /* @var $email Mage_Core_Model_Email_Template */ - if (Mage::getStoreConfigFlag(self::XML_PATH_SENDING_SET_RETURN_PATH)) { - $email->setReturnPath(Mage::getStoreConfig(self::XML_PATH_SUCCESS_EMAIL_IDENTITY)); - } + $email->sendTransactional( Mage::getStoreConfig(self::XML_PATH_SUCCESS_EMAIL_TEMPLATE), Mage::getStoreConfig(self::XML_PATH_SUCCESS_EMAIL_IDENTITY), @@ -516,10 +502,7 @@ public function sendUnsubscriptionEmail() $translate->setTranslateInline(false); $email = Mage::getModel('core/email_template'); - /* @var $email Mage_Core_Model_Email_Template */ - if (Mage::getStoreConfigFlag(self::XML_PATH_SENDING_SET_RETURN_PATH)) { - $email->setReturnPath(Mage::getStoreConfig(self::XML_PATH_UNSUBSCRIBE_EMAIL_IDENTITY)); - } + $email->sendTransactional( Mage::getStoreConfig(self::XML_PATH_UNSUBSCRIBE_EMAIL_TEMPLATE), Mage::getStoreConfig(self::XML_PATH_UNSUBSCRIBE_EMAIL_IDENTITY), diff --git a/app/code/core/Mage/Newsletter/Model/Template.php b/app/code/core/Mage/Newsletter/Model/Template.php index 121dd361e3..fcd306ac5d 100644 --- a/app/code/core/Mage/Newsletter/Model/Template.php +++ b/app/code/core/Mage/Newsletter/Model/Template.php @@ -72,7 +72,7 @@ public function validate() { $validators = array( 'template_code' => array(Zend_Filter_Input::ALLOW_EMPTY => false), - 'template_type' => 'Alnum', + 'template_type' => 'Int', 'template_sender_email' => 'EmailAddress', 'template_sender_name' => array(Zend_Filter_Input::ALLOW_EMPTY => false) ); diff --git a/app/code/core/Mage/Newsletter/etc/adminhtml.xml b/app/code/core/Mage/Newsletter/etc/adminhtml.xml new file mode 100644 index 0000000000..8099e45370 --- /dev/null +++ b/app/code/core/Mage/Newsletter/etc/adminhtml.xml @@ -0,0 +1,94 @@ + + + + + + Newsletter + 60 + + + Newsletter Templates + adminhtml/newsletter_template/ + admin/newsletter/template + + + Newsletter Queue + adminhtml/newsletter_queue/ + admin/newsletter/queue + + + Newsletter Subscribers + adminhtml/newsletter_subscriber/ + admin/newsletter/subscriber + + + Newsletter Problem Reports + adminhtml/newsletter_problem/ + admin/newsletter/problem + + + + + + + + + + + + + + Newsletter Section + + + + + + + Newsletter + 60 + + + Newsletter Problem Reports + + + Newsletter Queue + + + Newsletter Subscribers + + + Newsletter Templates + + + + + + + + diff --git a/app/code/core/Mage/Newsletter/etc/config.xml b/app/code/core/Mage/Newsletter/etc/config.xml index cc3aaa08cf..ca16a6080d 100644 --- a/app/code/core/Mage/Newsletter/etc/config.xml +++ b/app/code/core/Mage/Newsletter/etc/config.xml @@ -28,7 +28,7 @@ - 0.8.0 + 0.8.1 @@ -69,20 +69,7 @@ Mage_Newsletter - - core_setup - - - - core_write - - - - - core_read - - @@ -109,7 +96,6 @@ - singleton newsletter/observer subscribeCustomer @@ -118,78 +104,12 @@ - singleton newsletter/observer customerDeleted - - - Newsletter - 60 - - - Newsletter Templates - adminhtml/newsletter_template/ - admin/newsletter/template - - - Newsletter Queue - adminhtml/newsletter_queue/ - admin/newsletter/queue - - - Newsletter Subscribers - adminhtml/newsletter_subscriber/ - admin/newsletter/subscriber - - - Newsletter Problem Reports - adminhtml/newsletter_problem/ - admin/newsletter/problem - - - - - - - - - - - - - - Newsletter Section - - - - - - - Newsletter - 60 - - - Newsletter Problem Reports - - - Newsletter Queue - - - Newsletter Subscribers - - - Newsletter Templates - - - - - - - @@ -205,7 +125,6 @@ - singleton newsletter/observer subscribeCustomer @@ -214,7 +133,6 @@ - singleton newsletter/observer customerDeleted diff --git a/app/code/core/Mage/Newsletter/etc/system.xml b/app/code/core/Mage/Newsletter/etc/system.xml index a7a36df26f..2a00f4ef88 100644 --- a/app/code/core/Mage/Newsletter/etc/system.xml +++ b/app/code/core/Mage/Newsletter/etc/system.xml @@ -109,25 +109,6 @@ - - Sending Options - text - 2 - 1 - 0 - 0 - - - Set Return-Path - select - adminhtml/system_config_source_yesno - 1 - 1 - 0 - 0 - - - diff --git a/app/code/core/Mage/Newsletter/sql/newsletter_setup/mysql4-upgrade-0.8.0-0.8.1.php b/app/code/core/Mage/Newsletter/sql/newsletter_setup/mysql4-upgrade-0.8.0-0.8.1.php new file mode 100644 index 0000000000..a937bf8172 --- /dev/null +++ b/app/code/core/Mage/Newsletter/sql/newsletter_setup/mysql4-upgrade-0.8.0-0.8.1.php @@ -0,0 +1,44 @@ + + */ + +$installer = $this; +/* @var $installer Mage_Tax_Model_Mysql4_Setup */ + +$installer->startSetup(); + +$table = $installer->getTable('newsletter_queue_link'); + +$installer->getConnection()->addKey($table, 'IDX_NEWSLETTER_QUEUE_LINK_SEND_AT', array('queue_id', 'letter_sent_at')); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Ogone/Block/Form.php b/app/code/core/Mage/Ogone/Block/Form.php new file mode 100644 index 0000000000..c48c056edf --- /dev/null +++ b/app/code/core/Mage/Ogone/Block/Form.php @@ -0,0 +1,39 @@ +setTemplate('ogone/form.phtml'); + } +} diff --git a/app/code/core/Mage/Ogone/Block/Info.php b/app/code/core/Mage/Ogone/Block/Info.php new file mode 100644 index 0000000000..0be8fa4934 --- /dev/null +++ b/app/code/core/Mage/Ogone/Block/Info.php @@ -0,0 +1,41 @@ +setTemplate('ogone/info.phtml'); + } +} diff --git a/app/code/core/Mage/Ogone/Block/Paypage.php b/app/code/core/Mage/Ogone/Block/Paypage.php new file mode 100644 index 0000000000..b10e335df4 --- /dev/null +++ b/app/code/core/Mage/Ogone/Block/Paypage.php @@ -0,0 +1,41 @@ +setTemplate('ogone/paypage.phtml'); + return $this; + } +} diff --git a/app/code/core/Mage/Ogone/Block/Placeform.php b/app/code/core/Mage/Ogone/Block/Placeform.php new file mode 100644 index 0000000000..7eafcf4104 --- /dev/null +++ b/app/code/core/Mage/Ogone/Block/Placeform.php @@ -0,0 +1,90 @@ +getOrder()) { + $order = $this->getOrder(); + } else if ($this->getCheckout()->getLastRealOrderId()) { + $order = Mage::getModel('sales/order')->loadByIncrementId($this->getCheckout()->getLastRealOrderId()); + } else { + return null; + } + return $order; + } + + /** + * Get Form data by using ogone payment api + * + * @return array + */ + public function getFormData() + { + return $this->_getApi()->getFormFields($this->_getOrder()); + } + + /** + * Getting gateway url + * + * @return string + */ + public function getFormAction() + { + return $this->_getApi()->getConfig()->getGatewayPath(); + } +} diff --git a/app/code/core/Mage/Ogone/Helper/Data.php b/app/code/core/Mage/Ogone/Helper/Data.php new file mode 100644 index 0000000000..bc820095de --- /dev/null +++ b/app/code/core/Mage/Ogone/Helper/Data.php @@ -0,0 +1,68 @@ +_config = Mage::getSingleton('ogone/config'); + return $this; + } + + /** + * Return ogone config instance + * + * @return Mage_Ogone_Model_Config + */ + public function getConfig() + { + return $this->_config; + } + + /** + * Return debug flag by storeConfig + * + * @param int storeId + * @return bool + */ + public function getDebug($storeId=null) + { + return $this->getConfig()->getConfigData('debug_flag', $storeId); + } + + /** + * Flag witch prevent automatic invoice creation + * + * @return bool + */ + public function isInitializeNeeded() + { + return true; + } + + /** + * Redirect url to ogone submit form + * + * @return string + */ + public function getOrderPlaceRedirectUrl() + { + return Mage::getUrl('ogone/api/placeform', array('_secure' => true)); + } + + /** + * Return payment_action value from config area + * + * @return string + */ + public function getPaymentAction() + { + return $this->getConfig()->getConfigData('payment_action'); + } + + /** + * Rrepare params array to send it to gateway page via POST + * + * @param Mage_Sales_Model_Order + * @return array + */ + public function getFormFields($order) + { + if (empty($order)) { + if (!($order = $this->getOrder())) { + return array(); + } + } + $billingAddress = $order->getBillingAddress(); + $formFields = array(); + $formFields['PSPID'] = $this->getConfig()->getPSPID(); + $formFields['orderID'] = $order->getIncrementId(); + $formFields['amount'] = round($order->getBaseGrandTotal()*100); + $formFields['currency'] = Mage::app()->getStore()->getBaseCurrencyCode(); + $formFields['language'] = Mage::app()->getLocale()->getLocaleCode(); + + $formFields['CN'] = $this->_translate($billingAddress->getFirstname().' '.$billingAddress->getLastname()); + $formFields['EMAIL'] = $order->getCustomerEmail(); + $formFields['ownerZIP'] = $billingAddress->getPostcode(); + $formFields['ownercty'] = $billingAddress->getCountry(); + $formFields['ownertown']= $this->_translate($billingAddress->getCity()); + $formFields['COM'] = $this->_translate($this->_getOrderDescription($order)); + $formFields['ownertelno'] = $billingAddress->getTelephone(); + $formFields['owneraddress'] = $this->_translate(str_replace("\n", ' ',$billingAddress->getStreet(-1))); + + $paymentAction = $this->_getOgonePaymentOperation(); + if ($paymentAction ) { + $formFields['operation'] = $paymentAction; + } + + $secretCode = $this->getConfig()->getShaOutCode(); + $secretSet = $formFields['orderID'] . $formFields['amount'] . $formFields['currency'] . + $formFields['PSPID'] . $paymentAction . $secretCode; + + $formFields['SHASign'] = Mage::helper('ogone')->shaCrypt($secretSet); + + $formFields['homeurl'] = $this->getConfig()->getHomeUrl(); + $formFields['catalogurl'] = $this->getConfig()->getHomeUrl(); + $formFields['accepturl'] = $this->getConfig()->getAcceptUrl(); + $formFields['declineurl'] = $this->getConfig()->getDeclineUrl(); + $formFields['excteptionurl'] = $this->getConfig()->getExceptionUrl(); + $formFields['cancelurl'] = $this->getConfig()->getCancelUrl(); + + if ($this->getConfig()->getConfigData('template')=='ogone') { + $formFields['TP']= ''; + $formFields['PMListType'] = $this->getConfig()->getConfigData('pmlist'); + } else { + $formFields['TP']= $this->getConfig()->getPayPageTemplate(); + } + $formFields['TITLE'] = $this->_translate($this->getConfig()->getConfigData('html_title')); + $formFields['BGCOLOR'] = $this->getConfig()->getConfigData('bgcolor'); + $formFields['TXTCOLOR'] = $this->getConfig()->getConfigData('txtcolor'); + $formFields['TBLBGCOLOR'] = $this->getConfig()->getConfigData('tblbgcolor'); + $formFields['TBLTXTCOLOR'] = $this->getConfig()->getConfigData('tbltxtcolor'); + $formFields['BUTTONBGCOLOR'] = $this->getConfig()->getConfigData('buttonbgcolor'); + $formFields['BUTTONTXTCOLOR'] = $this->getConfig()->getConfigData('buttontxtcolor'); + $formFields['FONTTYPE'] = $this->getConfig()->getConfigData('fonttype'); + $formFields['LOGO'] = $this->getConfig()->getConfigData('logo'); + return $formFields; + } + + /** + * to translate UTF 8 to ISO 8859-1 + * Ogone system is only compatible with iso-8859-1 and does not (yet) fully support the utf-8 + */ + protected function _translate($text) + { + return htmlentities(iconv("UTF-8", "ISO-8859-1", $text)); + } + + /** + * Get Ogone Payment Action value + * + * @param string + * @return string + */ + protected function _getOgonePaymentOperation() + { + $value = $this->getPaymentAction(); + if ($value==Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE) { + $value = Mage_Ogone_Model_Api::OGONE_AUTHORIZE_ACTION; + } elseif ($value==Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE) { + $value = Mage_Ogone_Model_Api::OGONE_AUTHORIZE_CAPTURE_ACTION; + } + return $value; + } + + /** + * get formated order description + * + * @param Mage_Sales_Model_Order + * @return string + */ + protected function _getOrderDescription($order) + { + $invoiceDesc = ''; + $lengs = 0; + foreach ($order->getAllItems() as $item) { + if ($item->getParentItem()) { + continue; + } + if (Mage::helper('core/string')->strlen($invoiceDesc.$item->getName()) > 10000) { + break; + } + $invoiceDesc .= $item->getName() . ', '; + } + return Mage::helper('core/string')->substr($invoiceDesc, 0, -2); + } +} diff --git a/app/code/core/Mage/Ogone/Model/Api/Debug.php b/app/code/core/Mage/Ogone/Model/Api/Debug.php new file mode 100644 index 0000000000..342ef7938f --- /dev/null +++ b/app/code/core/Mage/Ogone/Model/Api/Debug.php @@ -0,0 +1,40 @@ +_init('ogone/api_debug'); + } +} diff --git a/app/code/core/Mage/Ogone/Model/Config.php b/app/code/core/Mage/Ogone/Model/Config.php new file mode 100644 index 0000000000..61717736d6 --- /dev/null +++ b/app/code/core/Mage/Ogone/Model/Config.php @@ -0,0 +1,151 @@ +decrypt($this->getConfigData('secret_key_in', $storeId)); + } + + /** + * Return SHA1-out crypt key from config. Setup on admin place. + * @param int $storeId + * @return string + */ + public function getShaOutCode($storeId=null) + { + return Mage::helper('core')->decrypt($this->getConfigData('secret_key_out', $storeId)); + } + + /** + * Return gateway path, get from confing. Setup on admin place. + * + * @param int $storeId + * @return string + */ + public function getGatewayPath($storeId=null) + { + return $this->getConfigData('ogone_gateway', $storeId); + } + + /** + * Get PSPID, affiliation name in ogone system + * + * @param int $storeId + * @return string + */ + public function getPSPID($storeId=null) + { + return $this->getConfigData('pspid', $storeId); + } + + /** + * Get paypage template for magento style templates using + * + * @return string + */ + public function getPayPageTemplate() + { + return Mage::getUrl('ogone/api/paypage'); + } + + /** + * Return url which ogone system will use as accept + * + * @return string + */ + public function getAcceptUrl() + { + return Mage::getUrl('ogone/api/accept'); + } + + /** + * Return url which ogone system will use as decline url + * + * @return string + */ + public function getDeclineUrl() + { + return Mage::getUrl('ogone/api/decline'); + } + + /** + * Return url which ogone system will use as exception url + * + * @return string + */ + public function getExceptionUrl() + { + return Mage::getUrl('ogone/api/exception'); + } + + /** + * Return url which ogone system will use as cancel url + * + * @return string + */ + public function getCancelUrl() + { + return Mage::getUrl('ogone/api/cancel'); + } + + /** + * Return url which ogone system will use as our magento home url on ogone success page + * + * @return string + */ + public function getHomeUrl() + { + return Mage::getUrl('checkout/cart'); + } +} diff --git a/app/code/core/Mage/Ogone/Model/Mysql4/Api/Debug.php b/app/code/core/Mage/Ogone/Model/Mysql4/Api/Debug.php new file mode 100644 index 0000000000..1f491e2702 --- /dev/null +++ b/app/code/core/Mage/Ogone/Model/Mysql4/Api/Debug.php @@ -0,0 +1,40 @@ +_init('ogone/api_debug', 'debug_id'); + } +} diff --git a/app/code/core/Mage/Ogone/Model/Source/PaymentAction.php b/app/code/core/Mage/Ogone/Model/Source/PaymentAction.php new file mode 100644 index 0000000000..57ee4e3c7e --- /dev/null +++ b/app/code/core/Mage/Ogone/Model/Source/PaymentAction.php @@ -0,0 +1,45 @@ + '', 'label' => Mage::helper('ogone')->__('Ogone default Operation')), + array('value' => Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE, 'label' => Mage::helper('ogone')->__('Authorization')), + array('value' => Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE, 'label' => Mage::helper('ogone')->__('Direct Sale')), + ); + } +} diff --git a/app/code/core/Mage/Ogone/Model/Source/Pmlist.php b/app/code/core/Mage/Ogone/Model/Source/Pmlist.php new file mode 100644 index 0000000000..2b06ebe2e1 --- /dev/null +++ b/app/code/core/Mage/Ogone/Model/Source/Pmlist.php @@ -0,0 +1,45 @@ + Mage_Ogone_Model_Api::PMLIST_HORISONTAL_LEFT, 'label' => Mage::helper('ogone')->__('Horizontally grouped logo with group name on left')), + array('value' => Mage_Ogone_Model_Api::PMLIST_HORISONTAL, 'label' => Mage::helper('ogone')->__('Horizontally grouped logo with no group name')), + array('value' => Mage_Ogone_Model_Api::PMLIST_VERTICAL, 'label' => Mage::helper('ogone')->__('Verical list')), + ); + } +} diff --git a/app/code/core/Mage/Ogone/Model/Source/Template.php b/app/code/core/Mage/Ogone/Model/Source/Template.php new file mode 100644 index 0000000000..86e36398f4 --- /dev/null +++ b/app/code/core/Mage/Ogone/Model/Source/Template.php @@ -0,0 +1,44 @@ + Mage_Ogone_Model_Api::TEMPLATE_OGONE, 'label' => Mage::helper('ogone')->__('Ogone')), + array('value' => Mage_Ogone_Model_Api::TEMPLATE_MAGENTO, 'label' => Mage::helper('ogone')->__('Magento')), + ); + } +} diff --git a/app/code/core/Mage/Ogone/controllers/ApiController.php b/app/code/core/Mage/Ogone/controllers/ApiController.php new file mode 100644 index 0000000000..ecc8320ab6 --- /dev/null +++ b/app/code/core/Mage/Ogone/controllers/ApiController.php @@ -0,0 +1,474 @@ +_order)) { + $orderId = $this->getRequest()->getParam('orderID'); + $this->_order = Mage::getModel('sales/order'); + $this->_order->loadByIncrementId($orderId); + } + return $this->_order; + } + + /** + * Validation of incoming Ogone data + * + * @return bool + */ + protected function _validateOgoneData() + { + $params = $this->getRequest()->getParams(); + $secureKey = $this->_getApi()->getConfig()->getShaInCode(); + $secureSet = $this->_getSHAInSet($params, $secureKey); + + if ($this->_getApi()->getDebug()) { + $debug = Mage::getModel('ogone/api_debug') + ->setDir('in') + ->setUrl($this->getRequest()->getPathInfo()) + ->setData('data',http_build_query($this->getRequest()->getParams())) + ->save(); + } + + if (Mage::helper('ogone')->shaCryptValidation($secureSet, $params['SHASIGN'])!=true) { + $this->_getCheckout()->addError($this->__('Hash is not valid')); + return false; + } + + $order = $this->_getOrder(); + if (!$order->getId()){ + $this->_getCheckout()->addError($this->__('Order is not valid')); + return false; + } + + return true; + } + + /** + * Load place from layout to make POST on ogone + */ + public function placeformAction() + { + $lastIncrementId = $this->_getCheckout()->getLastRealOrderId(); + if ($lastIncrementId) { + $order = Mage::getModel('sales/order'); + $order->loadByIncrementId($lastIncrementId); + if ($order->getId()) { + $order->setState(Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, Mage_Ogone_Model_Api::PENDING_OGONE_STATUS, Mage::helper('ogone')->__('Start ogone processing')); + $order->save(); + + if ($this->_getApi()->getDebug()) { + $debug = Mage::getModel('ogone/api_debug') + ->setDir('out') + ->setUrl($this->getRequest()->getPathInfo()) + ->setData('data', http_build_query($this->_getApi()->getFormFields($order))) + ->save(); + } + } + } + + $this->_getCheckout()->getQuote()->setIsActive(false)->save(); + $this->_getCheckout()->setOgoneQuoteId($this->_getCheckout()->getQuoteId()); + $this->_getCheckout()->setOgoneLastSuccessQuoteId($this->_getCheckout()->getLastSuccessQuoteId()); + $this->_getCheckout()->clear(); + + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * Display our pay page, need to ogone payment with external pay page mode * + */ + public function paypageAction() + { + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * Action to control postback data from ogone + * + */ + public function postBackAction() + { + if (!$this->_validateOgoneData()) { + $this->getResponse()->setHeader("Status", "404 Not Found"); + } + + $this->_offlineProcess(); + } + + /** + * Action to process ogone offline data + * + */ + public function offlineProcessAction() + { + if (!$this->_validateOgoneData()) { + $this->getResponse()->setHeader("Status","404 Not Found"); + } + $this->_offlineProcess(); + } + + /** + * Made offline ogone data processing, depending of incoming statuses + */ + protected function _offlineProcess() + { + $status = $this->getRequest()->getParam('STATUS'); + switch ($status) { + case Mage_Ogone_Model_Api::OGONE_AUTHORIZED : + case Mage_Ogone_Model_Api::OGONE_AUTH_PROCESSING: + case Mage_Ogone_Model_Api::OGONE_PAYMENT_REQUESTED_STATUS : + $this->_acceptProcess(); + break; + case Mage_Ogone_Model_Api::OGONE_AUTH_REFUZED: + case Mage_Ogone_Model_Api::OGONE_PAYMENT_INCOMPLETE: + case Mage_Ogone_Model_Api::OGONE_TECH_PROBLEM: + $this->_declineProcess(); + break; + case Mage_Ogone_Model_Api::OGONE_AUTH_UKNKOWN_STATUS: + case Mage_Ogone_Model_Api::OGONE_PAYMENT_UNCERTAIN_STATUS: + $this->_exceptionProcess(); + break; + default: + $this->_cancelProcess(); + } + } + + /** + * when payment gateway accept the payment, it will land to here + * need to change order status as processed ogone + * update transaction id + * + */ + public function acceptAction() + { + if (!$this->_validateOgoneData()) { + $this->_redirect('checkout/cart'); + return; + } + $this->_acceptProcess(); + } + + /** + * Process success action by accept url + */ + protected function _acceptProcess() + { + $params = $this->getRequest()->getParams(); + $order = $this->_getOrder(); + + $this->_getCheckout()->setLastSuccessQuoteId($this->_getCheckout()->getOgoneLastSuccessQuoteId()); + + $this->_prepareCCInfo($order, $params); + $order->getPayment()->setLastTransId($params['PAYID']); + + try{ + if ($this->_getApi()->getPaymentAction()==Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE) { + $this->_processDirectSale(); + } else { + $this->_processAuthorize(); + } + }catch(Exception $e) { + $this->_getCheckout()->addError(Mage::helper('ogone')->__('Order can\'t save')); + $this->_redirect('checkout/cart'); + return; + } + } + + /** + * Process Configured Payment Action: Direct Sale, create invoce if state is Pending + * + */ + protected function _processDirectSale() + { + $order = $this->_getOrder(); + $status = $this->getRequest()->getParam('STATUS'); + try{ + if ($status == Mage_Ogone_Model_Api::OGONE_AUTH_PROCESSING) { + $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, Mage_Ogone_Model_Api::WAITING_AUTHORIZATION, Mage::helper('ogone')->__('Authorization Waiting from Ogone')); + $order->save(); + }elseif ($order->getState()==Mage_Sales_Model_Order::STATE_PENDING_PAYMENT) { + if ($status == Mage_Ogone_Model_Api::OGONE_AUTHORIZED) { + if ($order->getStatus() != Mage_Sales_Model_Order::STATE_PENDING_PAYMENT) { + $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, Mage_Ogone_Model_Api::PROCESSING_OGONE_STATUS, Mage::helper('ogone')->__('Processed by Ogone')); + } + } else { + $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, Mage_Ogone_Model_Api::PROCESSED_OGONE_STATUS, Mage::helper('ogone')->__('Processed by Ogone')); + } + + if (!$order->getInvoiceCollection()->getSize()) { + $invoice = $order->prepareInvoice(); + $invoice->register(); + $invoice->setState(Mage_Sales_Model_Order_Invoice::STATE_PAID); + $invoice->getOrder()->setIsInProcess(true); + + $transactionSave = Mage::getModel('core/resource_transaction') + ->addObject($invoice) + ->addObject($invoice->getOrder()) + ->save(); + $order->sendNewOrderEmail(); + } + } else { + $order->save(); + } + $this->_redirect('checkout/onepage/success'); + return; + } catch (Exception $e) { + $this->_getCheckout()->addError(Mage::helper('ogone')->__('Order can\'t save')); + $this->_redirect('checkout/cart'); + return; + } + } + + /** + * Process Configured Payment Actions: Authorized, Default operation + * just place order + */ + protected function _processAuthorize() + { + $order = $this->_getOrder(); + $status = $this->getRequest()->getParam('STATUS'); + try { + if ($status == Mage_Ogone_Model_Api::OGONE_AUTH_PROCESSING) { + $order->setState(Mage_Sales_Model_Order::STATE_NEW, Mage_Ogone_Model_Api::WAITING_AUTHORIZATION, Mage::helper('ogone')->__('Authorization Waiting from Ogone')); + } else { + $order->setState(Mage_Sales_Model_Order::STATE_NEW, Mage_Ogone_Model_Api::PROCESSED_OGONE_STATUS, Mage::helper('ogone')->__('Processed by Ogone')); + $order->sendNewOrderEmail(); + } + $order->save(); + $this->_redirect('checkout/onepage/success'); + return; + } catch(Exception $e) { + $this->_getCheckout()->addError(Mage::helper('ogone')->__('Order can\'t save')); + $this->_redirect('checkout/cart'); + return; + } + } + + /** + * We get some CC info from ogone, so we must save it + * + * @param Mage_Sales_Model_Order $order + * @param array $ccInfo + * + * @return Mage_Ogone_ApiController + */ + protected function _prepareCCInfo($order, $ccInfo) + { + $order->getPayment()->setCcOwner($ccInfo['CN']); + $order->getPayment()->setCcNumberEnc($ccInfo['CARDNO']); + $order->getPayment()->setCcLast4(substr($ccInfo['CARDNO'], -4)); + $order->getPayment()->setCcExpMonth(substr($ccInfo['ED'], 0, 2)); + $order->getPayment()->setCcExpYear(substr($ccInfo['ED'], 2, 2)); + return $this; + } + + + /** + * the payment result is uncertain + * exception status can be 52 or 92 + * need to change order status as processing ogone + * update transaction id + * + */ + public function exceptionAction() + { + if (!$this->_validateOgoneData()) { + $this->_redirect('checkout/cart'); + return; + } + $this->_exceptionProcess(); + } + + /** + * Process exception action by ogone exception url + */ + public function _exceptionProcess() + { + $params = $this->getRequest()->getParams(); + $order = $this->_getOrder(); + + $exception = ''; + switch($params['STATUS']) { + case Mage_Ogone_Model_Api::OGONE_PAYMENT_UNCERTAIN_STATUS : + $exception = Mage::helper('ogone')->__('Payment uncertain: A technical problem arose during payment process, giving unpredictable result'); + break; + case Mage_Ogone_Model_Api::OGONE_AUTH_UKNKOWN_STATUS : + $exception = Mage::helper('ogone')->__('Authorisation not known: A technical problem arose during authorisation process, giving unpredictable result'); + break; + default: + $exception = ''; + } + + if (!empty($exception)) { + try{ + $this->_prepareCCInfo($order, $params); + $order->getPayment()->setLastTransId($params['PAYID']); + $order->addStatusToHistory(Mage_Ogone_Model_Api::PROCESSING_OGONE_STATUS, $exception); + $order->save(); + $this->_getCheckout()->addError($exception); + }catch(Exception $e) { + $this->_getCheckout()->addError(Mage::helper('ogone')->__('Order can not be save for system reason')); + } + } else { + $this->_getCheckout()->addError(Mage::helper('ogone')->__('Exception not defined')); + } + + $this->_redirect('checkout/onepage/success'); + return; + } + + /** + * when payment got decline + * need to change order status to cancelled + * take the user back to shopping cart + * + */ + public function declineAction() + { + if (!$this->_validateOgoneData()) { + $this->_redirect('checkout/cart'); + return; + } + $this->_getCheckout()->setQuoteId($this->_getCheckout()->getOgoneQuoteId()); + $this->_declineProcess(); + return $this; + } + + /** + * Process decline action by ogone decline url + */ + protected function _declineProcess() + { + $status = Mage_Ogone_Model_Api::DECLINE_OGONE_STATUS; + $comment = Mage::helper('ogone')->__('Declined Order on ogone side'); + $this->_getCheckout()->addError(Mage::helper('ogone')->__('Payment transaction has been declined.')); + $this->_cancelOrder($status, $comment); + } + + /** + * when user cancel the payment + * change order status to cancelled + * need to rediect user to shopping cart + * + * @return Mage_Ogone_ApiController + */ + public function cancelAction() + { + if (!$this->_validateOgoneData()) { + $this->_redirect('checkout/cart'); + return; + } + $this->_getCheckout()->setQuoteId($this->_getCheckout()->getOgoneQuoteId()); + $this->_cancelProcess(); + return $this; + } + + /** + * Process cancel action by cancel url + * + * @return Mage_Ogone_ApiController + */ + public function _cancelProcess() + { + $status = Mage_Ogone_Model_Api::CANCEL_OGONE_STATUS; + $comment = Mage::helper('ogone')->__('Order canceled on ogone side'); + $this->_cancelOrder($status, $comment); + return $this; + } + + /** + * Cancel action, used for decline and cancel processes + * + * @return Mage_Ogone_ApiController + */ + protected function _cancelOrder($status, $comment='') + { + $order = $this->_getOrder(); + try{ + $order->cancel(); + $order->setState(Mage_Sales_Model_Order::STATE_CANCELED, $status, $comment); + $order->save(); + }catch(Exception $e) { + $this->_getCheckout()->addError(Mage::helper('ogone')->__('Order can not be canceled for system reason')); + } + + $this->_redirect('checkout/cart'); + return $this; + } + + /** + * Return set of data which is ready for SHA crypt + * + * @param array $data + * @param string $key + * + * @return string + */ + protected function _getSHAInSet($params, $key) + { + return $params['orderID'] . $params['currency'] . $params['amount'] . + $params['PM'] . $params['ACCEPTANCE'] . $params['STATUS']. $params['CARDNO'] . $params['PAYID'] . + $params['NCERROR'] . $params['BRAND'] . $key; + } +} diff --git a/app/code/core/Mage/Ogone/etc/config.xml b/app/code/core/Mage/Ogone/etc/config.xml new file mode 100644 index 0000000000..1e7590e917 --- /dev/null +++ b/app/code/core/Mage/Ogone/etc/config.xml @@ -0,0 +1,112 @@ + + + + + + 0.0.1 + + + + + + Mage_Ogone_Model + ogone_mysql4 + + + Mage_Ogone_Model_Mysql4 + + ogone_api_debug + + + + + + + Mage_Ogone + + + + + + Mage_Ogone_Block + + + + + + Pending Ogone + Cancelled Ogone + Declined Ogone + Processing Ogone Payment + Processed Ogone Payment + Waiting Authorization + + + + + + + + + ogone.xml + + + + + /ogone/api + + + + standard + + Mage_Ogone + ogone + + + + + + + + + ogone/api + Ogone + + authorize + ogone + https://secure.ogone.com/ncol/test/orderstandard.asp + pending_ogone + cancel_ogone + decline_ogone + processing_ogone + processed_ogone + waiting_authorozation + + + + diff --git a/app/code/core/Mage/Ogone/etc/system.xml b/app/code/core/Mage/Ogone/etc/system.xml new file mode 100644 index 0000000000..b70d8411a4 --- /dev/null +++ b/app/code/core/Mage/Ogone/etc/system.xml @@ -0,0 +1,236 @@ + + + + + + + + Ogone + + + +Signing up with Ogone +Please enter the correct post back url and offline processiong url in Ogone configuration +post back url example: http://myMagentoStore.com/ogone/api/postBack +offline processing url example: http://myMagentoStore.com/ogone/api/offlineProcess + + +]]> + text + 800 + 1 + 1 + 1 + + + Enabled + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + Debug + select + adminhtml/system_config_source_yesno + 2 + 1 + 1 + 0 + + + Title + text + 3 + 1 + 1 + 1 + + + PSPID + text + PSPID is a case sensitive field + 4 + 1 + 1 + 0 + + + SHA Signature (used by Magento) + + Use in Feedback data checking by Magento + ]]> + adminhtml/system_config_backend_encrypted + obscure + 5 + 1 + 1 + 0 + + + SHA Signature (used by Ogone) + + Use in data checking by Ogone before the payment. + ]]> + adminhtml/system_config_backend_encrypted + obscure + 6 + 1 + 1 + 0 + + + Gate Way url + text + For production, replace "test" with "prod" Example: https://secure.ogone.com/ncol/prod/orderstandard.asp + 7 + 1 + 1 + 0 + + + Payment Action + select + ogone/source_paymentAction + If you choose "No Operation", Ogone will use default Payment procedure setup in Ogone configuration + 8 + 1 + 1 + 0 + + + Payment Template + select + ogone/source_template + If you choose "Ogone Template", you will need to fill in all the following fields. + 10 + 1 + 1 + 0 + + + Title of the Ogone Template + text + 11 + 1 + 1 + 1 + + + Background color of Ogone Template + text + + 12 + 1 + 1 + 1 + + + Text Color of the Ogone Template + text + + 13 + 1 + 1 + 1 + + + Table background color of the Ogone Template + text + + 14 + 1 + 1 + 1 + + + Table text color of the Ogone Template + text + + 15 + 1 + 1 + 1 + + + Button background color of the Ogone Template + text + 16 + 1 + 1 + 1 + + + Button text color of the Ogone Template + text + + 17 + 1 + 1 + 1 + + + Font Family of the Ogone Template + text + + 18 + 1 + 1 + 1 + + + Logo of the Ogone Template + text + The URL must be absolute and stored on a secure server. + ]]> + 19 + 1 + 1 + 1 + + + Layout of Payment Methods + select + ogone/source_pmlist + You can arrange the layout/list of the payment methods on Ogone payment page + 20 + 1 + 1 + 1 + + + + + + + diff --git a/app/code/core/Mage/Ogone/sql/ogone_setup/mysql4-install-0.0.1.php b/app/code/core/Mage/Ogone/sql/ogone_setup/mysql4-install-0.0.1.php new file mode 100644 index 0000000000..36391bcc71 --- /dev/null +++ b/app/code/core/Mage/Ogone/sql/ogone_setup/mysql4-install-0.0.1.php @@ -0,0 +1,46 @@ +startSetup(); + +$installer->run(" + +DROP TABLE IF EXISTS `{$this->getTable('ogone/api_debug')}`; +CREATE TABLE `{$this->getTable('ogone/api_debug')}` ( + `debug_id` int(10) unsigned NOT NULL auto_increment, + `dir` enum('in', 'out'), + `debug_at` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + `url` varchar(255), + `data` text, + PRIMARY KEY (`debug_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +"); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Oscommerce/Block/Adminhtml/Order/View.php b/app/code/core/Mage/Oscommerce/Block/Adminhtml/Order/View.php index 046a6608ed..7ef7e00853 100644 --- a/app/code/core/Mage/Oscommerce/Block/Adminhtml/Order/View.php +++ b/app/code/core/Mage/Oscommerce/Block/Adminhtml/Order/View.php @@ -36,10 +36,10 @@ public function __construct() { parent::__construct(); $this->setTemplate('oscommerce/order/view.phtml'); - + } - + protected function _prepareLayout() { $this->setChild('backButton', @@ -48,10 +48,11 @@ protected function _prepareLayout() 'label' => Mage::helper('oscommerce')->__('Back'), 'id' => 'back_button', 'name' => 'back_button', + 'element_name' => 'back_button', 'class' => 'scalable back', 'onclick'=> "setLocation('".$this->getBackUrl()."')", )) - ); + ); } /** * Retrieve order model object @@ -92,8 +93,8 @@ public function getUrl($params='', $params2=array()) public function getBackButtonHtml() { return $this->getChildHtml('backButton'); - } - + } + public function getBackUrl() { return $this->getUrl("*/*/"); diff --git a/app/code/core/Mage/Oscommerce/etc/adminhtml.xml b/app/code/core/Mage/Oscommerce/etc/adminhtml.xml new file mode 100644 index 0000000000..a359402e7a --- /dev/null +++ b/app/code/core/Mage/Oscommerce/etc/adminhtml.xml @@ -0,0 +1,80 @@ + + + + + + + + + + osCommerce + oscommerce/adminhtml_import + + + + + + + + + osCommerce Orders + oscommerce/adminhtml_order + 100 + + + + + + + + + + + + osCommerce Orders + 100 + + + + + + + + + osCommerce + 100 + + + + + + + + + + diff --git a/app/code/core/Mage/Oscommerce/etc/config.xml b/app/code/core/Mage/Oscommerce/etc/config.xml index f4bd9f73e9..cbf32e85cf 100755 --- a/app/code/core/Mage/Oscommerce/etc/config.xml +++ b/app/code/core/Mage/Oscommerce/etc/config.xml @@ -80,20 +80,7 @@ Mage_Oscommerce - - core_setup - - - - core_write - - - - - core_read - - pdo_mysql @@ -115,62 +102,10 @@ - - - - - - - osCommerce - oscommerce/adminhtml_import - - - - - - - - - osCommerce Orders - oscommerce/adminhtml_order - 100 - - - - - - - - - - - - osCommerce Orders - 100 - - - - - - - - - osCommerce - 100 - - - - - - - - - - singleton admin/observer actionPreDispatchAdmin diff --git a/app/code/core/Mage/Page/Block/Html/Head.php b/app/code/core/Mage/Page/Block/Html/Head.php index 8494983e65..73bc5d9bb6 100644 --- a/app/code/core/Mage/Page/Block/Html/Head.php +++ b/app/code/core/Mage/Page/Block/Html/Head.php @@ -24,44 +24,107 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Html page block * * @category Mage * @package Mage_Page - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Page_Block_Html_Head extends Mage_Core_Block_Template { + /** + * Initialize template + * + */ protected function _construct() { $this->setTemplate('page/html/head.phtml'); } - public function addCss($name, $params="") + /** + * Add CSS file to HEAD entity + * + * @param string $name + * @param string $params + * @return Mage_Page_Block_Html_Head + */ + public function addCss($name, $params = "") { $this->addItem('skin_css', $name, $params); return $this; } - public function addJs($name, $params="") + /** + * Add JavaScript file to HEAD entity + * + * @param string $name + * @param string $params + * @return Mage_Page_Block_Html_Head + */ + public function addJs($name, $params = "") { $this->addItem('js', $name, $params); return $this; } - public function addCssIe($name, $params="") + /** + * Add CSS file for Internet Explorer only to HEAD entity + * + * @param string $name + * @param string $params + * @return Mage_Page_Block_Html_Head + */ + public function addCssIe($name, $params = "") { $this->addItem('skin_css', $name, $params, 'IE'); return $this; } - public function addJsIe($name, $params="") + /** + * Add JavaScript file for Internet Explorer only to HEAD entity + * + * @param string $name + * @param string $params + * @return Mage_Page_Block_Html_Head + */ + public function addJsIe($name, $params = "") { $this->addItem('js', $name, $params, 'IE'); return $this; } + /** + * Add Link element to HEAD entity + * + * @param string $rel forward link types + * @param string $href URI for linked resource + * @return Mage_Page_Block_Html_Head + */ + public function addLinkRel($rel, $href) + { + $this->addItem('link_rel', $rel, $href); + return $this; + } + + /** + * Add HEAD Item + * + * Allowed types: + * - js + * - js_css + * - skin_js + * - skin_css + * - rss + * + * @param string $type + * @param string $name + * @param string $params + * @param string $if + * @param string $cond + * @return Mage_Page_Block_Html_Head + */ public function addItem($type, $name, $params=null, $if=null, $cond=null) { if ($type==='skin_css' && empty($params)) { @@ -77,22 +140,34 @@ public function addItem($type, $name, $params=null, $if=null, $cond=null) return $this; } + /** + * Remove Item from HEAD entity + * + * @param string $type + * @param string $name + * @return Mage_Page_Block_Html_Head + */ public function removeItem($type, $name) { unset($this->_data['items'][$type.'/'.$name]); return $this; } + /** + * Retrieve HEAD fragment HTML with CSS/JS/RSS definations + * + * @return string + */ public function getCssJsHtml() { -// return ''; - $lines = array(); + $lines = array(); $baseJs = Mage::getBaseUrl('js'); - $html = ''; + $html = ''; - $script = ''; + $script = ''; $stylesheet = ''; - $alternate = ''; + $alternate = ''; + $linkRel = ''; foreach ($this->_data['items'] as $item) { if (!is_null($item['cond']) && !$this->getData($item['cond'])) { @@ -122,6 +197,10 @@ public function getCssJsHtml() case 'rss': $lines[$if]['other'][] = sprintf($alternate, 'application/rss+xml'/*'text/xml' for IE?*/, $item['name'], $item['params']); break; + + case 'link_rel': + $lines[$if]['other'][] = sprintf($linkRel, $item['name'], $item['params']); + break; } } @@ -162,11 +241,19 @@ public function getCssJsHtml() return $html; } - public function getChunkedItems($items, $prefix='', $maxLen=450) + /** + * Retrieve Chunked Items + * + * @param array $items + * @param string $prefix + * @param int $maxLen + * @return array + */ + public function getChunkedItems($items, $prefix = '', $maxLen = 450) { $chunks = array(); - $chunk = $prefix; - foreach ($items as $i=>$item) { + $chunk = $prefix; + foreach ($items as $item) { if (strlen($chunk.','.$item)>$maxLen) { $chunks[] = $chunk; $chunk = $prefix; @@ -177,6 +264,11 @@ public function getChunkedItems($items, $prefix='', $maxLen=450) return $chunks; } + /** + * Retrieve Content Type + * + * @return string + */ public function getContentType() { if (empty($this->_data['content_type'])) { @@ -185,6 +277,11 @@ public function getContentType() return $this->_data['content_type']; } + /** + * Retrieve Media Type + * + * @return string + */ public function getMediaType() { if (empty($this->_data['media_type'])) { @@ -193,6 +290,11 @@ public function getMediaType() return $this->_data['media_type']; } + /** + * Retrieve Charset + * + * @return string + */ public function getCharset() { if (empty($this->_data['charset'])) { @@ -201,6 +303,12 @@ public function getCharset() return $this->_data['charset']; } + /** + * Set title element text + * + * @param string $title + * @return Mage_Page_Block_Html_Head + */ public function setTitle($title) { $this->_data['title'] = Mage::getStoreConfig('design/head/title_prefix').' '.$title @@ -208,6 +316,11 @@ public function setTitle($title) return $this; } + /** + * Retrieve title element text (encoded) + * + * @return string + */ public function getTitle() { if (empty($this->_data['title'])) { @@ -216,11 +329,21 @@ public function getTitle() return htmlspecialchars(html_entity_decode($this->_data['title'], ENT_QUOTES, 'UTF-8')); } + /** + * Retrieve default title text + * + * @return string + */ public function getDefaultTitle() { return Mage::getStoreConfig('design/head/default_title'); } + /** + * Retrieve content for description tag + * + * @return string + */ public function getDescription() { if (empty($this->_data['description'])) { @@ -229,6 +352,11 @@ public function getDescription() return $this->_data['description']; } + /** + * Retrieve content for keyvords tag + * + * @return string + */ public function getKeywords() { if (empty($this->_data['keywords'])) { @@ -237,6 +365,11 @@ public function getKeywords() return $this->_data['keywords']; } + /** + * Retrieve URL to robots file + * + * @return string + */ public function getRobots() { if (empty($this->_data['robots'])) { @@ -257,5 +390,4 @@ public function getIncludes() } return $this->_data['includes']; } - } diff --git a/app/code/core/Mage/Page/Model/Source/Layout.php b/app/code/core/Mage/Page/Model/Source/Layout.php index b490c5287b..edf2a76fab 100644 --- a/app/code/core/Mage/Page/Model/Source/Layout.php +++ b/app/code/core/Mage/Page/Model/Source/Layout.php @@ -63,7 +63,7 @@ public function getOptions() * * @return array */ - public function toOptionArray() + public function toOptionArray($withEmpty = false) { $options = array(); @@ -74,6 +74,10 @@ public function toOptionArray() ); } + if ($withEmpty) { + array_unshift($options, array('value'=>'', 'label'=>Mage::helper('page')->__('-- Please Select --'))); + } + return $options; } } diff --git a/app/code/core/Mage/Paybox/etc/config.xml b/app/code/core/Mage/Paybox/etc/config.xml index a8407c1809..a719fb61a2 100644 --- a/app/code/core/Mage/Paybox/etc/config.xml +++ b/app/code/core/Mage/Paybox/etc/config.xml @@ -51,20 +51,7 @@ Mage_Paybox Mage_Paybox_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - Mage_Paybox_Block diff --git a/app/code/core/Mage/Paygate/Model/Authorizenet.php b/app/code/core/Mage/Paygate/Model/Authorizenet.php index 361c78afe8..573590f303 100644 --- a/app/code/core/Mage/Paygate/Model/Authorizenet.php +++ b/app/code/core/Mage/Paygate/Model/Authorizenet.php @@ -207,6 +207,13 @@ public function refund(Varien_Object $payment, $amount) $payment->setAnetTransType(self::REQUEST_TYPE_CREDIT); $request = $this->_buildRequest($payment); $request->setXTransId($payment->getRefundTransactionId()); + + /** + * need to send last 4 digit credit card number to authorize.net + * otherwise it will give an error + */ + $request->setXCardNum($payment->getCcLast4()); + $result = $this->_postRequest($request); if ($result->getResponseCode()==self::RESPONSE_CODE_APPROVED) { diff --git a/app/code/core/Mage/Paygate/etc/config.xml b/app/code/core/Mage/Paygate/etc/config.xml index 5db0e60fce..3b949fa0f7 100644 --- a/app/code/core/Mage/Paygate/etc/config.xml +++ b/app/code/core/Mage/Paygate/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.0 + 0.7.1 @@ -50,20 +50,7 @@ Mage_Paygate - - core_setup - - - - core_write - - - - - core_read - - @@ -122,14 +109,14 @@ C Credit Card (Payflow Pro) - https://pilot-payflowpro.verisign.com/transaction + https://payflowpro.paypal.com/transaction MEDIUM 0 - - - 0 + + + 0 diff --git a/app/code/core/Mage/Paygate/etc/system.xml b/app/code/core/Mage/Paygate/etc/system.xml index 0831914602..839b315712 100644 --- a/app/code/core/Mage/Paygate/etc/system.xml +++ b/app/code/core/Mage/Paygate/etc/system.xml @@ -316,8 +316,9 @@ 1 1 - + URL + Live: <strong>https://payflowpro.paypal.com/transaction</strong><br />Test: <strong>https://pilot-payflowpro.paypal.com/transaction</strong> text 2 1 diff --git a/app/code/core/Mage/Paygate/sql/paygate_setup/mysql4-upgrade-0.7.0-0.7.1.php b/app/code/core/Mage/Paygate/sql/paygate_setup/mysql4-upgrade-0.7.0-0.7.1.php new file mode 100644 index 0000000000..aa0817ea83 --- /dev/null +++ b/app/code/core/Mage/Paygate/sql/paygate_setup/mysql4-upgrade-0.7.0-0.7.1.php @@ -0,0 +1,44 @@ +startSetup(); + +// replace transaction URLs - see http://integrationwizard.x.com/sdkupdate/step3.php +foreach (array( + 'pilot-payflowpro.verisign.com' => 'pilot-payflowpro.paypal.com', + 'test-payflow.verisign.com' => 'pilot-payflowpro.paypal.com', + 'payflow.verisign.com' => 'payflowpro.paypal.com', + ) as $from => $to) { + $installer->run(" + UPDATE {$installer->getTable('core/config_data')} SET `value` = REPLACE(`value`, '{$from}', '{$to}') + WHERE `path` = 'payment/verisign/url' + "); +} + +$installer->endSetup(); diff --git a/app/code/core/Mage/Payment/Helper/Data.php b/app/code/core/Mage/Payment/Helper/Data.php index 535eb4bba4..d04c974f7c 100644 --- a/app/code/core/Mage/Payment/Helper/Data.php +++ b/app/code/core/Mage/Payment/Helper/Data.php @@ -44,13 +44,13 @@ public function getMethodInstance($code) $key = self::XML_PATH_PAYMENT_METHODS.'/'.$code.'/model'; $class = Mage::getStoreConfig($key); if (!$class) { - Mage::throwException($this->__('Can not configuration for payment method with code: %s', $code)); + Mage::throwException($this->__('Cannot load configuration for payment method "%s"', $code)); } return Mage::getModel($class); } /** - * Retrieve available payment methods for store + * Get and sort available payment methods for specified or current store * * array structure: * $index => Varien_Simplexml_Element @@ -58,61 +58,34 @@ public function getMethodInstance($code) * @param mixed $store * @return array */ - public function getStoreMethods($store=null, $quote=null) + public function getStoreMethods($store = null, $quote = null) { $methods = Mage::getStoreConfig(self::XML_PATH_PAYMENT_METHODS, $store); $res = array(); foreach ($methods as $code => $methodConfig) { - $prefix = self::XML_PATH_PAYMENT_METHODS.'/'.$code.'/'; - - if (!Mage::getStoreConfigFlag($prefix.'active', $store)) { - continue; - } - if (!$model = Mage::getStoreConfig($prefix.'model', $store)) { + $prefix = self::XML_PATH_PAYMENT_METHODS . '/' . $code . '/'; + if (!$model = Mage::getStoreConfig($prefix . 'model', $store)) { continue; } - $methodInstance = Mage::getModel($model); - - if ($methodInstance instanceof Mage_Payment_Model_Method_Cc && !Mage::getStoreConfig($prefix.'cctypes')) { - /* if the payment method has credit card types configuration option - and no credit card type is enabled in configuration */ - continue; - } - - if ( !$methodInstance->isAvailable($quote) ) { + if (!$methodInstance->isAvailable($quote)) { /* if the payment method can not be used at this time */ continue; } - $sortOrder = (int)Mage::getStoreConfig($prefix.'sort_order', $store); + $sortOrder = (int)Mage::getStoreConfig($prefix . 'sort_order', $store); $methodInstance->setSortOrder($sortOrder); $methodInstance->setStore($store); -// while (isset($res[$sortOrder])) { -// $sortOrder++; -// } -// $res[$sortOrder] = $methodInstance; $res[] = $methodInstance; } -// ksort($res); - //die('!'); - //echo ''; - //var_dump( (array)$res); usort($res, array($this, '_sortMethods')); - //var_dump((array)$res); - // echo ''; return $res; } protected function _sortMethods($a, $b) { - // var_dump($a); if (is_object($a)) { - //var_dump($a->getData()); - //var_dump($a->sort_order); - //die (); - return (int)$a->sort_order < (int)$b->sort_order ? -1 : ((int)$a->sort_order > (int)$b->sort_order ? 1 : 0); } return 0; @@ -154,4 +127,4 @@ public function getInfoBlock(Mage_Payment_Model_Info $info) $block->setInfo($info); return $block; } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Payment/Model/Method/Abstract.php b/app/code/core/Mage/Payment/Model/Method/Abstract.php index b7e180ec84..97220b40f6 100644 --- a/app/code/core/Mage/Payment/Model/Method/Abstract.php +++ b/app/code/core/Mage/Payment/Model/Method/Abstract.php @@ -447,13 +447,21 @@ public function prepareSave() } /** - * Return true if the method can be used at this time + * Check whether payment method can be used * + * @param Mage_Sales_Model_Quote * @return bool */ - public function isAvailable($quote=null) + public function isAvailable($quote = null) { - return true; + $checkResult = new StdClass; + $checkResult->isAvailable = (bool)(int)$this->getConfigData('active', ($quote ? $quote->getStoreId() : null)); + Mage::dispatchEvent('payment_method_is_active', array( + 'result' => $checkResult, + 'method_instance' => $this, + 'quote' => $quote, + )); + return $checkResult->isAvailable; } /** @@ -467,5 +475,4 @@ public function initialize($paymentAction, $stateObject) { return $this; } - -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Payment/Model/Method/Cc.php b/app/code/core/Mage/Payment/Model/Method/Cc.php index 0311ccbf2d..4262279f16 100644 --- a/app/code/core/Mage/Payment/Model/Method/Cc.php +++ b/app/code/core/Mage/Payment/Model/Method/Cc.php @@ -137,7 +137,7 @@ public function validate() $errorMsg = $this->_getHelper()->__('Credit card type is not allowed for this payment method'); } - //validate credit card verification number + //validate credit card verification number if ($errorMsg === false && $this->hasVerification()) { $verifcationRegEx = $this->getVerificationRegEx(); $regExp = isset($verifcationRegEx[$info->getCcType()]) ? $verifcationRegEx[$info->getCcType()] : ''; @@ -154,7 +154,7 @@ public function validate() return $this; } - public function hasVerification() + public function hasVerification() { $configData = $this->getConfigData('useccv'); if(is_null($configData)){ @@ -163,7 +163,7 @@ public function hasVerification() return (bool) $configData; } - public function getVerificationRegEx() + public function getVerificationRegEx() { $verificationExpList = array( 'VI' => '/^[0-9]{3}$/', // Visa @@ -240,4 +240,14 @@ public function validateCcNumOther($ccNumber) return preg_match('/^\\d+$/', $ccNumber); } -} \ No newline at end of file + /** + * Check whether there are CC types set in configuration + * + * @return bool + */ + public function isAvailable($quote = null) + { + return $this->getConfigData('cctypes', ($quote ? $quote->getStoreId() : null)) + && parent::isAvailable($quote); + } +} diff --git a/app/code/core/Mage/Payment/Model/Method/Free.php b/app/code/core/Mage/Payment/Model/Method/Free.php index 6a1dc2fee6..ba50e9b44f 100644 --- a/app/code/core/Mage/Payment/Model/Method/Free.php +++ b/app/code/core/Mage/Payment/Model/Method/Free.php @@ -42,20 +42,14 @@ class Mage_Payment_Model_Method_Free extends Mage_Payment_Model_Method_Abstract protected $_code = 'free'; /** - * Check method is available + * Check whether method is available * * @param Mage_Sales_Model_Quote $quote * @return bool */ - public function isAvailable($quote=null) + public function isAvailable($quote = null) { - if (is_null($quote)) { - return false; - } - - if (Mage::app()->getStore()->roundPrice($quote->getGrandTotal()) == 0) { - return true; - } - return false; + return parent::isAvailable($quote) && (!empty($quote)) + && (Mage::app()->getStore()->roundPrice($quote->getGrandTotal()) == 0); } } diff --git a/app/code/core/Mage/Payment/etc/adminhtml.xml b/app/code/core/Mage/Payment/etc/adminhtml.xml new file mode 100644 index 0000000000..af8c74e4b7 --- /dev/null +++ b/app/code/core/Mage/Payment/etc/adminhtml.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + Payment Methods Section + + + + + + + + + + diff --git a/app/code/core/Mage/Payment/etc/config.xml b/app/code/core/Mage/Payment/etc/config.xml index a518c4fac3..6e1847bb63 100644 --- a/app/code/core/Mage/Payment/etc/config.xml +++ b/app/code/core/Mage/Payment/etc/config.xml @@ -44,9 +44,6 @@ Mage_Payment - - core_setup - @@ -93,7 +90,6 @@ - singleton payment/observer salesOrderBeforeSave @@ -124,25 +120,6 @@ - - - - - - - - - - Payment Methods Section - - - - - - - - - diff --git a/app/code/core/Mage/Paypal/Block/Link/Shortcut.php b/app/code/core/Mage/Paypal/Block/Link/Shortcut.php index 98291af078..6e17d7bb62 100644 --- a/app/code/core/Mage/Paypal/Block/Link/Shortcut.php +++ b/app/code/core/Mage/Paypal/Block/Link/Shortcut.php @@ -48,13 +48,17 @@ public function getImageUrl() return 'https://www.paypal.com/'.$locale.'/i/btn/btn_xpressCheckout.gif'; } + /** + * Check whether method is available and render HTML + * + * @return string + */ public function _toHtml() { - if (Mage::getStoreConfigFlag('payment/paypal_express/active') - && Mage::getSingleton('checkout/session')->getQuote()->validateMinimumAmount()) { - return parent::_toHtml(); + $quote = Mage::getSingleton('checkout/session')->getQuote(); + if (!Mage::getModel('paypal/express')->isAvailable($quote) || !$quote->validateMinimumAmount()) { + return ''; } - - return ''; + return parent::_toHtml(); } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Paypal/Model/Standard.php b/app/code/core/Mage/Paypal/Model/Standard.php index a5c7e2e50a..348950b768 100644 --- a/app/code/core/Mage/Paypal/Model/Standard.php +++ b/app/code/core/Mage/Paypal/Model/Standard.php @@ -490,7 +490,7 @@ public function initialize($paymentAction, $stateObject) { $state = Mage_Sales_Model_Order::STATE_PENDING_PAYMENT; $stateObject->setState($state); - $stateObject->setStatus(Mage::getSingleton('sales/order_config')->getStateDefaultStatus($state)); + $stateObject->setStatus('pending_paypal'); $stateObject->setIsNotified(false); } } diff --git a/app/code/core/Mage/Paypal/etc/adminhtml.xml b/app/code/core/Mage/Paypal/etc/adminhtml.xml new file mode 100644 index 0000000000..f23a25bc08 --- /dev/null +++ b/app/code/core/Mage/Paypal/etc/adminhtml.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + PayPal Section + + + + + + + + + + diff --git a/app/code/core/Mage/Paypal/etc/config.xml b/app/code/core/Mage/Paypal/etc/config.xml index 93d7e78619..3c0d591d1f 100644 --- a/app/code/core/Mage/Paypal/etc/config.xml +++ b/app/code/core/Mage/Paypal/etc/config.xml @@ -50,24 +50,25 @@ Mage_Paypal Mage_Paypal_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - Mage_Paypal_Block + + + + Pending PayPal + + + + + + + + + + @@ -110,25 +111,6 @@ - - - - - - - - - - PayPal Section - - - - - - - - - diff --git a/app/code/core/Mage/PaypalUk/etc/config.xml b/app/code/core/Mage/PaypalUk/etc/config.xml index 8199705649..b9ed29868d 100644 --- a/app/code/core/Mage/PaypalUk/etc/config.xml +++ b/app/code/core/Mage/PaypalUk/etc/config.xml @@ -55,24 +55,25 @@ Mage_PaypalUk - - core_setup - - - - core_write - - - - - core_read - - Mage_PaypalUk_Block + + + + Pending PayPal + + + + + + + + + + diff --git a/app/code/core/Mage/Poll/Block/ActivePoll.php b/app/code/core/Mage/Poll/Block/ActivePoll.php index df4e197ca0..90cdf4614c 100755 --- a/app/code/core/Mage/Poll/Block/ActivePoll.php +++ b/app/code/core/Mage/Poll/Block/ActivePoll.php @@ -79,7 +79,7 @@ public function __construct() $this->assign('poll', $poll) ->assign('poll_answers', $pollAnswers) - ->assign('action', Mage::getUrl('poll/vote/add', array('poll_id' => $pollId))); + ->assign('action', Mage::getUrl('poll/vote/add', array('poll_id' => $pollId, '_secure' => true))); $this->_voted = Mage::getModel('poll/poll')->isVoted($pollId); Mage::getSingleton('core/session')->setJustVotedPoll(false); diff --git a/app/code/core/Mage/Poll/Model/Mysql4/Poll/Collection.php b/app/code/core/Mage/Poll/Model/Mysql4/Poll/Collection.php index a105cdba5e..348fe775aa 100644 --- a/app/code/core/Mage/Poll/Model/Mysql4/Poll/Collection.php +++ b/app/code/core/Mage/Poll/Model/Mysql4/Poll/Collection.php @@ -63,7 +63,7 @@ public function addFieldToFilter($field, $condition=null) */ public function addStoresFilter($store) { - return $this->addStoresFilter($store); + return $this->addStoreFilter($store); } /** diff --git a/app/code/core/Mage/Poll/etc/config.xml b/app/code/core/Mage/Poll/etc/config.xml index 96432d60aa..33e531db77 100644 --- a/app/code/core/Mage/Poll/etc/config.xml +++ b/app/code/core/Mage/Poll/etc/config.xml @@ -100,20 +100,7 @@ Mage_Poll - - core_setup - - - - core_write - - - - - core_read - - diff --git a/app/code/core/Mage/ProductAlert/Model/Observer.php b/app/code/core/Mage/ProductAlert/Model/Observer.php index 65bfbc5ad5..80e996603d 100644 --- a/app/code/core/Mage/ProductAlert/Model/Observer.php +++ b/app/code/core/Mage/ProductAlert/Model/Observer.php @@ -150,9 +150,12 @@ protected function _processPrice(Mage_ProductAlert_Model_Email $email) } $product->setCustomerGroupId($customer->getGroupId()); if ($alert->getPrice() > $product->getFinalPrice()) { + $productPrice = $product->getFinalPrice(); + $product->setFinalPrice(Mage::helper('tax')->getPrice($product, $productPrice)); + $product->setPrice(Mage::helper('tax')->getPrice($product, $product->getPrice())); $email->addPriceProduct($product); - $alert->setPrice($product->getFinalPrice()); + $alert->setPrice($productPrice); $alert->setLastSendDate(Mage::getModel('core/date')->gmtDate()); $alert->setSendCount($alert->getSendCount() + 1); $alert->setStatus(1); diff --git a/app/code/core/Mage/ProductAlert/etc/config.xml b/app/code/core/Mage/ProductAlert/etc/config.xml index 2f067f7277..46e29306d9 100644 --- a/app/code/core/Mage/ProductAlert/etc/config.xml +++ b/app/code/core/Mage/ProductAlert/etc/config.xml @@ -60,20 +60,7 @@ Mage_ProductAlert - - core_setup - - - - core_setup - - - - - core_setup - - diff --git a/app/code/core/Mage/ProductAlert/etc/system.xml b/app/code/core/Mage/ProductAlert/etc/system.xml index 0924faae93..f64446ddab 100644 --- a/app/code/core/Mage/ProductAlert/etc/system.xml +++ b/app/code/core/Mage/ProductAlert/etc/system.xml @@ -35,7 +35,7 @@ 250 1 1 - 0 + 1 Allow alert when product price changes @@ -63,7 +63,7 @@ 2 1 1 - 0 + 1 Stock alert Email Template @@ -72,7 +72,7 @@ 4 1 1 - 0 + 1 Alert Email Sender @@ -81,7 +81,7 @@ 5 1 1 - 0 + 1 diff --git a/app/code/core/Mage/Protx/etc/config.xml b/app/code/core/Mage/Protx/etc/config.xml index 2274dee2c5..6e4d89430f 100644 --- a/app/code/core/Mage/Protx/etc/config.xml +++ b/app/code/core/Mage/Protx/etc/config.xml @@ -49,20 +49,7 @@ Mage_Protx - - core_setup - - - - core_write - - - - - core_read - - Mage_Protx_Block diff --git a/app/code/core/Mage/Rating/etc/adminhtml.xml b/app/code/core/Mage/Rating/etc/adminhtml.xml new file mode 100644 index 0000000000..190d88adc6 --- /dev/null +++ b/app/code/core/Mage/Rating/etc/adminhtml.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + Manage Ratings + + + + + + + + + + diff --git a/app/code/core/Mage/Rating/etc/config.xml b/app/code/core/Mage/Rating/etc/config.xml index 8cc68b3703..4c5c828a13 100644 --- a/app/code/core/Mage/Rating/etc/config.xml +++ b/app/code/core/Mage/Rating/etc/config.xml @@ -72,20 +72,7 @@ Mage_Rating - - core_setup - - - - core_write - - - - - core_read - - @@ -106,25 +93,6 @@ - - - - - - - - - - Manage Ratings - - - - - - - - - diff --git a/app/code/core/Mage/Reports/Block/Product/Abstract.php b/app/code/core/Mage/Reports/Block/Product/Abstract.php index 561f956bf8..95dfeac5c2 100644 --- a/app/code/core/Mage/Reports/Block/Product/Abstract.php +++ b/app/code/core/Mage/Reports/Block/Product/Abstract.php @@ -111,7 +111,9 @@ public function getItemsCollection() ->addAttributeToSelect($attributes) ->addIndexFilter() ->excludeProductIds($this->_getModel()->getExcludeProductIds()) - ->setAddedAtOrder(); + ->setAddedAtOrder() + ->setPageSize($this->getPageSize()) + ->setCurPage(1); Mage::getSingleton('catalog/product_visibility') ->addVisibleInSiteFilterToCollection($this->_collection); diff --git a/app/code/core/Mage/Reports/Block/Product/Widget/Compared.php b/app/code/core/Mage/Reports/Block/Product/Widget/Compared.php new file mode 100644 index 0000000000..dfaee5bcdf --- /dev/null +++ b/app/code/core/Mage/Reports/Block/Product/Widget/Compared.php @@ -0,0 +1,39 @@ + + */ +class Mage_Reports_Block_Product_Widget_Compared + extends Mage_Reports_Block_Product_Compared + implements Mage_Cms_Block_Widget_Interface +{ + +} diff --git a/app/code/core/Mage/Reports/Block/Product/Widget/Viewed.php b/app/code/core/Mage/Reports/Block/Product/Widget/Viewed.php new file mode 100644 index 0000000000..6ffe495402 --- /dev/null +++ b/app/code/core/Mage/Reports/Block/Product/Widget/Viewed.php @@ -0,0 +1,39 @@ + + */ +class Mage_Reports_Block_Product_Widget_Viewed + extends Mage_Reports_Block_Product_Viewed + implements Mage_Cms_Block_Widget_Interface +{ + +} diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Order/Collection.php b/app/code/core/Mage/Reports/Model/Mysql4/Order/Collection.php index 33e2249895..9be0a19800 100644 --- a/app/code/core/Mage/Reports/Model/Mysql4/Order/Collection.php +++ b/app/code/core/Mage/Reports/Model/Mysql4/Order/Collection.php @@ -298,7 +298,11 @@ public function setStoreIds($storeIds) ->addExpressionAttributeToSelect( 'refunded', 'SUM({{base_total_refunded}})', - array('base_total_refunded')); + array('base_total_refunded')) + ->addExpressionAttributeToSelect( + 'profit', + 'SUM({{base_total_paid}}) - SUM({{base_total_refunded}}) - SUM({{base_total_invoiced_cost}})', + array('base_total_paid', 'base_total_refunded', 'base_total_invoiced_cost')); } else { $this->addExpressionAttributeToSelect( 'subtotal', @@ -327,7 +331,11 @@ public function setStoreIds($storeIds) ->addExpressionAttributeToSelect( 'refunded', 'SUM({{base_total_refunded}}*{{base_to_global_rate}})', - array('base_total_refunded', 'base_to_global_rate')); + array('base_total_refunded', 'base_to_global_rate')) + ->addExpressionAttributeToSelect( + 'profit', + 'SUM({{base_total_paid}}*{{base_to_global_rate}}) - SUM({{base_total_refunded}}*{{base_to_global_rate}}) - SUM({{base_total_invoiced_cost}}*{{base_to_global_rate}})', + array('base_total_paid', 'base_total_refunded', 'base_total_invoiced_cost', 'base_to_global_rate')); } return $this; @@ -460,4 +468,22 @@ public function getSelectCountSql() return $sql; } + + /** + * Add period filter by created_at attribute + * + * @param string $period + * @return Mage_Reports_Model_Mysql4_Order_Collection + */ + public function addCreateAtPeriodFilter($period) + { + list($from, $to) = $this->getDateRange($period, 0, 0, true); + + $this->addAttributeToFilter('created_at', array( + 'from' => $from->toString(Varien_Date::DATETIME_INTERNAL_FORMAT), + 'to' => $to->toString(Varien_Date::DATETIME_INTERNAL_FORMAT) + )); + + return $this; + } } diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Product/Index/Abstract.php b/app/code/core/Mage/Reports/Model/Mysql4/Product/Index/Abstract.php index 3936b91f41..6093598a1c 100644 --- a/app/code/core/Mage/Reports/Model/Mysql4/Product/Index/Abstract.php +++ b/app/code/core/Mage/Reports/Model/Mysql4/Product/Index/Abstract.php @@ -103,7 +103,7 @@ public function purgeVisitorByCustomer(Mage_Reports_Model_Product_Index_Abstract $where = $this->_getWriteAdapter()->quoteInto('customer_id=?', $object->getCustomerId()); $bind = array( - 'visitor_id' => 0, + 'visitor_id' => null, ); $this->_getWriteAdapter()->update($this->getMainTable(), $bind, $where); diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Product/Lowstock/Collection.php b/app/code/core/Mage/Reports/Model/Mysql4/Product/Lowstock/Collection.php index 0a208b9a9d..0521f07366 100644 --- a/app/code/core/Mage/Reports/Model/Mysql4/Product/Lowstock/Collection.php +++ b/app/code/core/Mage/Reports/Model/Mysql4/Product/Lowstock/Collection.php @@ -1,174 +1,237 @@ - - */ - -class Mage_Reports_Model_Mysql4_Product_Lowstock_Collection extends Mage_Reports_Model_Mysql4_Product_Collection -{ - protected $_inventoryItemResource = null; - protected $_inventoryItemJoined = false; - protected $_inventoryItemTableAlias = 'lowstock_inventory_item'; - - /** - * @return string - */ - protected function _getInventoryItemResource() - { - if (is_null($this->_inventoryItemResource)) { - $this->_inventoryItemResource = Mage::getResourceSingleton('cataloginventory/stock_item'); - } - return $this->_inventoryItemResource; - } - - /** - * @return string - */ - protected function _getInventoryItemTable() - { - return $this->_getInventoryItemResource()->getMainTable(); - } - - /** - * @return string - */ - protected function _getInventoryItemIdField() - { - return $this->_getInventoryItemResource()->getIdFieldName(); - } - - /** - * @return string - */ - protected function _getInventoryItemTableAlias() - { - return $this->_inventoryItemTableAlias; - } - - /** - * @param array|string $fields - * @return string - */ - protected function _processInventoryItemFields($fields) - { - if (is_array($fields)) { - $aliasArr = array(); - foreach ($fields as &$field) { - if ( is_string($field) && strpos($field, '(') === false ) { - $field = sprintf('%s.%s', $this->_getInventoryItemTableAlias(), $field); - } - } - unset($field); - return $fields; - } - return sprintf('%s.%s', $this->_getInventoryItemTableAlias(), $fields); - } - - /** - * Join cataloginventory_stock_item table for further - * stock_item values filters - * @return Mage_Reports_Model_Mysql4_Product_Collection - */ - public function joinInventoryItem($fields=array()) { - if ( !$this->_inventoryItemJoined ) { - $this->getSelect()->join( - array($this->_getInventoryItemTableAlias() => $this->_getInventoryItemTable()), - sprintf('e.%s=%s.product_id', - $this->getEntity()->getEntityIdField(), - $this->_getInventoryItemTableAlias() - ), - array() - ); - $this->_inventoryItemJoined = true; - } - if (is_string($fields)) { - $fields = array($fields); - } - if (!empty($fields)) { - $this->getSelect()->columns($this->_processInventoryItemFields($fields)); - } - return $this; - } - - /** - * @param array|string $typeFilter - * @return Mage_Reports_Model_Mysql4_Product_Collection - */ - public function filterByProductType($typeFilter) - { - if (!is_string($typeFilter) && !is_array($typeFilter)) { - Mage::throwException( - Mage::helper('catalog')->__('Wrong product type filter specified') - ); - } - $this->addAttributeToFilter('type_id', $typeFilter); - return $this; - } - - /** - * @return Mage_Reports_Model_Mysql4_Product_Collection - */ - public function filterByIsQtyProductTypes() - { - $this->filterByProductType( - array_keys(array_filter(Mage::helper('cataloginventory')->getIsQtyTypeIds())) - ); - return $this; - } - - /** - * @param int|null $storeId - * @return Mage_Reports_Model_Mysql4_Product_Collection - */ - public function useManageStockFilter($storeId=null) - { - $this->joinInventoryItem(); - $this->getSelect()->where(sprintf('IF(%s,%d,%s)=1', - $this->_processInventoryItemFields('use_config_manage_stock'), - (int) Mage::getStoreConfig(Mage_CatalogInventory_Model_Stock_Item::XML_PATH_MANAGE_STOCK,$storeId), - $this->_processInventoryItemFields('manage_stock'))); - return $this; - } - - /** - * @param int|null $storeId - * @return Mage_Reports_Model_Mysql4_Product_Collection - */ - public function useNotifyStockQtyFilter($storeId=null) - { - $this->joinInventoryItem(array('qty')); - $this->getSelect()->where(sprintf('qty < IF(%s,%d,%s)', - $this->_processInventoryItemFields('use_config_notify_stock_qty'), - (int) Mage::getStoreConfig(Mage_CatalogInventory_Model_Stock_Item::XML_PATH_NOTIFY_STOCK_QTY,$storeId), - $this->_processInventoryItemFields('notify_stock_qty'))); - return $this; - } -} + + */ +class Mage_Reports_Model_Mysql4_Product_Lowstock_Collection extends Mage_Reports_Model_Mysql4_Product_Collection +{ + /** + * CatalogInventory Stock Item Resource instance + * + * @var Mage_CatalogInventory_Model_Mysql4_Stock_Item + */ + protected $_inventoryItemResource = null; + + /** + * Flag about is joined CatalogInventory Stock Item + * + * @var bool + */ + protected $_inventoryItemJoined = false; + + /** + * Alias for CatalogInventory Stock Item Table + * + * @var string + */ + protected $_inventoryItemTableAlias = 'lowstock_inventory_item'; + + /** + * Retrieve CatalogInventory Stock Item Resource instance + * + * @return Mage_CatalogInventory_Model_Mysql4_Stock_Item + */ + protected function _getInventoryItemResource() + { + if (is_null($this->_inventoryItemResource)) { + $this->_inventoryItemResource = Mage::getResourceSingleton('cataloginventory/stock_item'); + } + return $this->_inventoryItemResource; + } + + /** + * Retrieve CatalogInventory Stock Item Table + * + * @return string + */ + protected function _getInventoryItemTable() + { + return $this->_getInventoryItemResource()->getMainTable(); + } + + /** + * Retrieve CatalogInventory Stock Item Table Id field name + * + * @return string + */ + protected function _getInventoryItemIdField() + { + return $this->_getInventoryItemResource()->getIdFieldName(); + } + + /** + * Retrieve alias for CatalogInventory Stock Item Table + * + * @return string + */ + protected function _getInventoryItemTableAlias() + { + return $this->_inventoryItemTableAlias; + } + + /** + * Add catalog inventory stock item field to select + * + * @param string $field + * @param string $alias + * @return Mage_Reports_Model_Mysql4_Product_Lowstock_Collection + */ + protected function _addInventoryItemFieldToSelect($field, $alias = null) + { + if (empty($alias)) { + $alias = $field; + } + + if (isset($this->_joinFields[$alias])) { + return $this; + } + + $this->_joinFields[$alias] = array( + 'table' => $this->_getInventoryItemTableAlias(), + 'field' => $field + ); + + $this->getSelect()->columns(array($alias => $field), $this->_getInventoryItemTableAlias()); + return $this; + } + + /** + * Retrieve catalog inventory stock item field correlation name + * + * @param string $field + * @return string + */ + protected function _getInventoryItemField($field) + { + return sprintf('%s.%s', $this->_getInventoryItemTableAlias(), $field); + } + + /** + * Join catalog inventory stock item table for further stock_item values filters + * + * @return Mage_Reports_Model_Mysql4_Product_Collection + */ + public function joinInventoryItem($fields = array()) + { + if (!$this->_inventoryItemJoined) { + $this->getSelect()->join( + array($this->_getInventoryItemTableAlias() => $this->_getInventoryItemTable()), + sprintf('`e`.`%s`=`%s`.`product_id`', + $this->getEntity()->getEntityIdField(), + $this->_getInventoryItemTableAlias() + ), + array() + ); + $this->_inventoryItemJoined = true; + } + + if (!is_array($fields)) { + if (empty($fields)) { + $fields = array(); + } else { + $fields = array($fields); + } + } + + foreach ($fields as $alias => $field) { + if (!is_string($alias)) { + $alias = null; + } + $this->_addInventoryItemFieldToSelect($field, $alias); + } + + return $this; + } + + /** + * Add filter by product type(s) + * + * @param array|string $typeFilter + * @return Mage_Reports_Model_Mysql4_Product_Collection + */ + public function filterByProductType($typeFilter) + { + if (!is_string($typeFilter) && !is_array($typeFilter)) { + Mage::throwException( + Mage::helper('catalog')->__('Wrong product type filter specified') + ); + } + $this->addAttributeToFilter('type_id', $typeFilter); + return $this; + } + + /** + * Add filter by product types from config + * Only types witch has QTY parameter + * + * @return Mage_Reports_Model_Mysql4_Product_Collection + */ + public function filterByIsQtyProductTypes() + { + $this->filterByProductType( + array_keys(array_filter(Mage::helper('cataloginventory')->getIsQtyTypeIds())) + ); + return $this; + } + + /** + * Add Use Manage Stock Condition to collection + * + * @param int|null $storeId + * @return Mage_Reports_Model_Mysql4_Product_Collection + */ + public function useManageStockFilter($storeId = null) + { + $this->joinInventoryItem(); + $this->getSelect()->where(sprintf('IF(%s,%d,%s)=1', + $this->_getInventoryItemField('use_config_manage_stock'), + (int) Mage::getStoreConfig(Mage_CatalogInventory_Model_Stock_Item::XML_PATH_MANAGE_STOCK,$storeId), + $this->_getInventoryItemIdField('manage_stock'))); + return $this; + } + + /** + * Add Notify Stock Qty Condition to collection + * + * @param int $storeId + * @return Mage_Reports_Model_Mysql4_Product_Collection + */ + public function useNotifyStockQtyFilter($storeId = null) + { + $this->joinInventoryItem(array('qty')); + $this->getSelect()->where(sprintf('qty < IF(%s,%d,%s)', + $this->_getInventoryItemField('use_config_notify_stock_qty'), + (int)Mage::getStoreConfig(Mage_CatalogInventory_Model_Stock_Item::XML_PATH_NOTIFY_STOCK_QTY,$storeId), + $this->_getInventoryItemField('notify_stock_qty'))); + return $this; + } +} diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Quote/Collection.php b/app/code/core/Mage/Reports/Model/Mysql4/Quote/Collection.php index 296719dd8a..aff458fe28 100644 --- a/app/code/core/Mage/Reports/Model/Mysql4/Quote/Collection.php +++ b/app/code/core/Mage/Reports/Model/Mysql4/Quote/Collection.php @@ -36,6 +36,8 @@ class Mage_Reports_Model_Mysql4_Quote_Collection extends Mage_Sales_Model_Mysql4 { protected $_joinedFields = array(); + protected $_map = array('fields' => array('store_id' => 'main_table.store_id')); + public function prepareForAbandonedReport($storeIds, $filter = null) { $this->addFieldToFilter('items_count', array('neq' => '0')) @@ -43,13 +45,24 @@ public function prepareForAbandonedReport($storeIds, $filter = null) ->addSubtotal($storeIds, $filter) ->addCustomerData($filter) ->setOrder('updated_at'); - if (is_array($storeIds)) { - $this->addFieldToFilter('main_table.store_id', array('in' => $storeIds)); + $this->addFieldToFilter('store_id', array('in' => $storeIds)); } return $this; } + /** + * Add store ids to filter + * + * @param array $storeIds + * @return Mage_Reports_Model_Mysql4_Quote_Collection + */ + public function addStoreFilter($storeIds) + { + $this->addFieldToFilter('store_id', array('in' => $storeIds)); + return $this; + } + public function addCustomerData($filter = null) { $customerEntity = Mage::getResourceSingleton('customer/customer'); diff --git a/app/code/core/Mage/Reports/etc/adminhtml.xml b/app/code/core/Mage/Reports/etc/adminhtml.xml new file mode 100644 index 0000000000..df9a60e46c --- /dev/null +++ b/app/code/core/Mage/Reports/etc/adminhtml.xml @@ -0,0 +1,300 @@ + + + + + + Reports + 80 + + + Sales + + + Sales Report + adminhtml/report_sales/sales + + + Tax + adminhtml/report_sales/tax + + + Shipping + adminhtml/report_sales/shipping + + + Total invoiced + adminhtml/report_sales/invoiced + + + Total refunded + adminhtml/report_sales/refunded + + + Coupons + adminhtml/report_sales/coupons + + + + + Shopping Cart + + + Products in carts + adminhtml/report_shopcart/product + + + Abandoned carts + adminhtml/report_shopcart/abandoned + + + + + + Products + + + Bestsellers + adminhtml/report_product/ordered + + + Products Ordered + adminhtml/report_product/sold + + + Most Viewed + adminhtml/report_product/viewed + + + Low stock + adminhtml/report_product/lowstock + + + Downloads + adminhtml/report_product/downloads + + + + + Customers + + + New Accounts + adminhtml/report_customer/accounts + + + Customers by orders total + adminhtml/report_customer/totals + + + Customers by number of orders + adminhtml/report_customer/orders + + + + + + Reviews + + + Customers Reviews + adminhtml/report_review/customer + + + Products Reviews + adminhtml/report_review/product + + + + + Tags + + + Customers + adminhtml/report_tag/customer + + + Products + adminhtml/report_tag/product + + + + Popular + adminhtml/report_tag/popular + + + + + Search Terms + adminhtml/report/search + + + + + + + + + + + Reports + 80 + + + Sales + + + Sales Report + + + Tax + + + Shipping + + + Total invoiced + + + Total refunded + + + Coupons + + + + + Shopping Cart + + + Products in carts + + + Abandoned carts + + + + + Products + + + Bestsellers + + + Products Ordered + + + Most Viewed + + + Low stock + + + Downloads + + + + + Customers + + + New Accounts + + + Customers by orders total + + + Customers by number of orders + + + + + Reviews + + + Customers Reviews + + + Products Reviews + + + + + Tags + + + Customers + + + Popular + + + Products + + + + + Search Terms + + + + + + + + + Reports + + + + + + + + + + diff --git a/app/code/core/Mage/Reports/etc/config.xml b/app/code/core/Mage/Reports/etc/config.xml index 2a0aae3760..2ace0cb32f 100644 --- a/app/code/core/Mage/Reports/etc/config.xml +++ b/app/code/core/Mage/Reports/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.8 + 0.7.9 @@ -61,26 +61,12 @@ Mage_Reports - - core_setup - - - - core_write - - - - - core_read - - - singleton reports/event_observer eventClean @@ -99,278 +85,6 @@ - - - - Reports - 80 - - - Sales - - - Sales Report - adminhtml/report_sales/sales - - - Tax - adminhtml/report_sales/tax - - - Shipping - adminhtml/report_sales/shipping - - - Total invoiced - adminhtml/report_sales/invoiced - - - Total refunded - adminhtml/report_sales/refunded - - - Coupons - adminhtml/report_sales/coupons - - - - - Shopping Cart - - - Products in carts - adminhtml/report_shopcart/product - - - Abandoned carts - adminhtml/report_shopcart/abandoned - - - - - - Products - - - Bestsellers - adminhtml/report_product/ordered - - - Products Ordered - adminhtml/report_product/sold - - - Most Viewed - adminhtml/report_product/viewed - - - Low stock - adminhtml/report_product/lowstock - - - Downloads - adminhtml/report_product/downloads - - - - - Customers - - - New Accounts - adminhtml/report_customer/accounts - - - Customers by orders total - adminhtml/report_customer/totals - - - Customers by number of orders - adminhtml/report_customer/orders - - - - - - Reviews - - - Customers Reviews - adminhtml/report_review/customer - - - Products Reviews - adminhtml/report_review/product - - - - - Tags - - - Customers - adminhtml/report_tag/customer - - - Products - adminhtml/report_tag/product - - - - Popular - adminhtml/report_tag/popular - - - - - Search Terms - adminhtml/report/search - - - - - - - - - - - Reports - 80 - - - Sales - - - Sales Report - - - Tax - - - Shipping - - - Total invoiced - - - Total refunded - - - Coupons - - - - - Shopping Cart - - - Products in carts - - - Abandoned carts - - - - - Products - - - Bestsellers - - - Products Ordered - - - Most Viewed - - - Low stock - - - Downloads - - - - - Customers - - - New Accounts - - - Customers by orders total - - - Customers by number of orders - - - - - Reviews - - - Customers Reviews - - - Products Reviews - - - - - Tags - - - Customers - - - Popular - - - Products - - - - - Search Terms - - - - - - - - - Reports - - - - - - - - - @@ -393,7 +107,6 @@ - singleton reports/event_observer catalogProductCompareRemoveProduct @@ -402,7 +115,6 @@ - singleton reports/event_observer customerLogin @@ -411,7 +123,6 @@ - singleton reports/event_observer customerLogout @@ -420,7 +131,6 @@ - singleton reports/event_observer catalogProductView @@ -429,7 +139,6 @@ - singleton reports/event_observer sendfriendProduct @@ -438,7 +147,6 @@ - singleton reports/event_observer catalogProductCompareAddProduct @@ -447,7 +155,6 @@ - singleton reports/event_observer catalogProductCompareClear @@ -456,7 +163,6 @@ - singleton reports/event_observer checkoutCartAddProduct @@ -465,7 +171,6 @@ - singleton reports/event_observer wishlistAddProduct @@ -474,7 +179,6 @@ - singleton reports/event_observer wishlistShare diff --git a/app/code/core/Mage/Reports/etc/system.xml b/app/code/core/Mage/Reports/etc/system.xml index 0b0b1838da..5f9a6cf67e 100644 --- a/app/code/core/Mage/Reports/etc/system.xml +++ b/app/code/core/Mage/Reports/etc/system.xml @@ -93,7 +93,7 @@ 0 - Month-To-Date Starts + Current Month Starts select adminhtml/report_config_form_field_mtdStart Select day of the month diff --git a/app/code/core/Mage/Reports/etc/widget.xml b/app/code/core/Mage/Reports/etc/widget.xml new file mode 100644 index 0000000000..a66e66cae3 --- /dev/null +++ b/app/code/core/Mage/Reports/etc/widget.xml @@ -0,0 +1,65 @@ + + + + + + Recently Viewed Products + List of Products Recently Viewed by Visitor + + + 1 + 1 + 5 + Number of Products to display + text + + + 1 + reports/product/widget/viewed.phtml + + + + + Recently Compared Products + List of Products Recently Compared and Removed from the Compare List by Visitor + + + 1 + 1 + 5 + Number of Products to display + text + + + 1 + reports/product/widget/compared.phtml + + + + + diff --git a/app/code/core/Mage/Reports/sql/reports_setup/mysql4-upgrade-0.7.8-0.7.9.php b/app/code/core/Mage/Reports/sql/reports_setup/mysql4-upgrade-0.7.8-0.7.9.php new file mode 100644 index 0000000000..8459ae2872 --- /dev/null +++ b/app/code/core/Mage/Reports/sql/reports_setup/mysql4-upgrade-0.7.8-0.7.9.php @@ -0,0 +1,33 @@ +startSetup(); +$installer->run("ALTER TABLE {$this->getTable('report_viewed_product_index')} CHANGE `visitor_id` `visitor_id` INT( 10 ) UNSIGNED NULL "); +$installer->endSetup(); diff --git a/app/code/core/Mage/Review/Block/Helper.php b/app/code/core/Mage/Review/Block/Helper.php index 6b367abd4c..737d3fc24d 100644 --- a/app/code/core/Mage/Review/Block/Helper.php +++ b/app/code/core/Mage/Review/Block/Helper.php @@ -33,7 +33,7 @@ */ class Mage_Review_Block_Helper extends Mage_Core_Block_Template { - private $_availableTemplates = array( + protected $_availableTemplates = array( 'default' => 'review/helper/summary.phtml', 'short' => 'review/helper/summary_short.phtml' ); diff --git a/app/code/core/Mage/Review/Model/Review.php b/app/code/core/Mage/Review/Model/Review.php index 8bb34a5d21..9453d14d3e 100644 --- a/app/code/core/Mage/Review/Model/Review.php +++ b/app/code/core/Mage/Review/Model/Review.php @@ -33,6 +33,14 @@ */ class Mage_Review_Model_Review extends Mage_Core_Model_Abstract { + + /** + * Event prefix for observer + * + * @var string + */ + protected $_eventPrefix = 'review'; + const ENTITY_PRODUCT = 1; const STATUS_APPROVED = 1; diff --git a/app/code/core/Mage/Review/etc/adminhtml.xml b/app/code/core/Mage/Review/etc/adminhtml.xml new file mode 100644 index 0000000000..a6f5192c48 --- /dev/null +++ b/app/code/core/Mage/Review/etc/adminhtml.xml @@ -0,0 +1,85 @@ + + + + + + + + Reviews and Ratings + + + Customer Reviews + + + Pending Reviews + adminhtml/catalog_product_review/pending/ + + + All Reviews + adminhtml/catalog_product_review/ + + + + + Manage Ratings + adminhtml/rating/ + + + + + + + + + + + + + + Reviews and Ratings + + + Customer Reviews + + + All Reviews + + + Pending Reviews + + + + + + + + + + + + diff --git a/app/code/core/Mage/Review/etc/config.xml b/app/code/core/Mage/Review/etc/config.xml index 70ee91e642..ca7b227191 100644 --- a/app/code/core/Mage/Review/etc/config.xml +++ b/app/code/core/Mage/Review/etc/config.xml @@ -66,20 +66,7 @@ Mage_Review - - core_setup - - - - core_write - - - - - core_read - - @@ -88,62 +75,6 @@ - - - - - Reviews and Ratings - - - Customer Reviews - - - Pending Reviews - adminhtml/catalog_product_review/pending/ - - - All Reviews - adminhtml/catalog_product_review/ - - - - - Manage Ratings - adminhtml/rating/ - - - - - - - - - - - - - - Reviews and Ratings - - - Customer Reviews - - - All Reviews - - - Pending Reviews - - - - - - - - - - - @@ -164,6 +95,16 @@ + + + + + rss/observer + reviewSaveAfter + + + + diff --git a/app/code/core/Mage/Rss/Block/Catalog/Category.php b/app/code/core/Mage/Rss/Block/Catalog/Category.php index 70978f0733..80344fd5ca 100644 --- a/app/code/core/Mage/Rss/Block/Catalog/Category.php +++ b/app/code/core/Mage/Rss/Block/Catalog/Category.php @@ -63,8 +63,7 @@ protected function _toHtml() 'link' => $newurl, 'charset' => 'UTF-8', ); -//echo ""; -//print_r($data); + $rssObj->_addHeader($data); $_collection = $category->getCollection(); @@ -81,18 +80,6 @@ protected function _toHtml() $layer->prepareProductCollection($productCollection); $productCollection->addCountToCategories($_collection); - /*if ($_collection->count()) { - foreach ($_collection as $_category){ - $data = array( - 'title' => $_category->getName(), - 'link' => $_category->getCategoryUrl(), - 'description' => $this->helper('rss')->__('Total Products: %s', $_category->getProductCount()), - ); - - $rssObj->_addEntry($data); - } - } - */ $category->getProductCollection()->setStoreId($storeId); /* only load latest 50 products @@ -100,35 +87,61 @@ protected function _toHtml() $_productCollection = $currentyCateogry ->getProductCollection() ->addAttributeToSort('updated_at','desc') + ->setVisibility(Mage::getSingleton('catalog/product_visibility')->getVisibleInCatalogIds()) ->setCurPage(1) ->setPageSize(50) ; -//echo "".$_productCollection->getSelect(); + if ($_productCollection->getSize()>0) { + $args = array('rssObj' => $rssObj); foreach ($_productCollection as $_product) { - $final_price = $_product->getFinalPrice(); - $description = ''. - ''. - ''.$_product->getDescription(). - ' Price:'.Mage::helper('core')->currency($_product->getPrice()). - ($_product->getPrice() != $final_price ? ' Special Price:'. Mage::helper('core')->currency($final_price) : ''). - ''. - ''. - '' - ; - $data = array( - 'title' => $_product->getName(), - 'link' => $_product->getProductUrl(), - 'description' => $description, - ); -//print_r($data); - $rssObj->_addEntry($data); + $args['product'] = $_product; + $this->addNewItemXmlCallback($args); } } } } return $rssObj->createRssXml(); + } + + /** + * Preparing data and adding to rss object + * + * @param array $args + */ + public function addNewItemXmlCallback($args) + { + $product = $args['product']; + $product->setAllowedInRss(true); + + Mage::dispatchEvent('rss_catalog_category_xml_callback', $args); + + if (!$product->getAllowedInRss()) { + return; + } + + $description = '' + . '' + . '' . $product->getDescription(); + + if ($product->getAllowedPriceInRss()) { + $description .= ' Price:'.Mage::helper('core')->currency($product->getPrice()); + if ($product->getPrice() != $product->getFinalPrice()) { + $description .= ' Special Price:' . Mage::helper('core')->currency($product->getFinalPrice()); + } + $description .= ''; + } + + $description .= ''; + $rssObj = $args['rssObj']; + $data = array( + 'title' => $product->getName(), + 'link' => $product->getProductUrl(), + 'description' => $description, + ); + $rssObj->_addEntry($data); } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Rss/Block/Catalog/New.php b/app/code/core/Mage/Rss/Block/Catalog/New.php index 422922b384..8b54f6d513 100644 --- a/app/code/core/Mage/Rss/Block/Catalog/New.php +++ b/app/code/core/Mage/Rss/Block/Catalog/New.php @@ -77,19 +77,24 @@ protected function _toHtml() ->addAttributeToSelect(array('special_price', 'special_from_date', 'special_to_date'), 'left') ; - Mage::getSingleton('catalog/product_status')->addVisibleFilterToCollection($products); - Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($products); -//echo $products->getSelect(); + $products->setVisibility(Mage::getSingleton('catalog/product_visibility')->getVisibleInCatalogIds()); + /* using resource iterator to load the data one by one instead of loading all at the same time. loading all data at the same time can cause the big memory allocation. */ + Mage::getSingleton('core/resource_iterator') ->walk($products->getSelect(), array(array($this, 'addNewItemXmlCallback')), array('rssObj'=> $rssObj, 'product'=>$product)); return $rssObj->createRssXml(); } + /** + * Preparing data and adding to rss object + * + * @param array $args + */ public function addNewItemXmlCallback($args) { $product = $args['product']; @@ -102,24 +107,31 @@ public function addNewItemXmlCallback($args) return; } + $allowedPriceInRss = $product->getAllowedPriceInRss(); + //$product->unsetData()->load($args['row']['entity_id']); $product->setData($args['row']); - $final_price = $product->getFinalPrice(); $description = ''. ''. - ''.$product->getDescription(). - ' Price:'.Mage::helper('core')->currency($product->getPrice()). - ($product->getPrice() != $final_price ? ' Special Price:'. Mage::helper('core')->currency($final_price) : ''). - ''. - ''. + ''.$product->getDescription(); + + if ($allowedPriceInRss) { + $description .= ' Price:' . Mage::helper('core')->currency($product->getPrice()); + if ($product->getPrice() != $product->getFinalPrice()){ + $description .= ' Special Price:' . Mage::helper('core')->currency($product->getFinalPrice()); + } + $description .= ''; + } + + $description .= ''. ''; + $rssObj = $args['rssObj']; $data = array( 'title' => $product->getName(), 'link' => $product->getProductUrl(), 'description' => $description, - - ); + ); $rssObj->_addEntry($data); } } \ No newline at end of file diff --git a/app/code/core/Mage/Rss/Block/Catalog/NotifyStock.php b/app/code/core/Mage/Rss/Block/Catalog/NotifyStock.php index 9676c28d5a..0c3340d146 100644 --- a/app/code/core/Mage/Rss/Block/Catalog/NotifyStock.php +++ b/app/code/core/Mage/Rss/Block/Catalog/NotifyStock.php @@ -29,12 +29,21 @@ * * @category Mage * @package Mage_Rss - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Rss_Block_Catalog_NotifyStock extends Mage_Rss_Block_Abstract { + + /** + * Cache tag constant for feed notify stock + * + * @var string + */ + const CACHE_TAG = 'block_html_rss_catalog_notifystock'; + protected function _construct() { + $this->setCacheTags(array(self::CACHE_TAG)); /* * setting cache to save the rss for 10 minutes */ diff --git a/app/code/core/Mage/Rss/Block/Catalog/Review.php b/app/code/core/Mage/Rss/Block/Catalog/Review.php index bae3cba6f5..8960393ce3 100644 --- a/app/code/core/Mage/Rss/Block/Catalog/Review.php +++ b/app/code/core/Mage/Rss/Block/Catalog/Review.php @@ -29,12 +29,21 @@ * * @category Mage * @package Mage_Rss - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Rss_Block_Catalog_Review extends Mage_Rss_Block_Abstract { + + /** + * Cache tag constant for feed reviews + * + * @var string + */ + const CACHE_TAG = 'block_html_rss_catalog_review'; + protected function _construct() { + $this->setCacheTags(array(self::CACHE_TAG)); /* * setting cache to save the rss for 10 minutes */ diff --git a/app/code/core/Mage/Rss/Block/Catalog/Special.php b/app/code/core/Mage/Rss/Block/Catalog/Special.php index 6fa5c48191..07eae45e0c 100644 --- a/app/code/core/Mage/Rss/Block/Catalog/Special.php +++ b/app/code/core/Mage/Rss/Block/Catalog/Special.php @@ -68,27 +68,19 @@ protected function _toHtml() ->joinTable('catalogrule/rule_product_price', 'product_id=entity_id', array('rule_price'=>'rule_price', 'rule_start_date'=>'latest_start_date'), $rulePriceWhere, 'left') ; -//public function join($table, $cond, $cols='*') $rulePriceCollection = Mage::getResourceModel('catalogrule/rule_product_price_collection') ->addFieldToFilter('website_id', $websiteId) ->addFieldToFilter('customer_group_id', $custGroup) ->addFieldToFilter('rule_date', $todayDate) ; -//echo $rulePriceCollection->getSelect(); + $productIds = $rulePriceCollection->getProductIds(); if (!empty($productIds)) { $specials->getSelect()->orWhere('e.entity_id in ('.implode(',',$productIds).')'); } - /* - *need to put status and visibility after orWhere clause for catalog price rule products - */ - Mage::getSingleton('catalog/product_status')->addVisibleFilterToCollection($specials); - Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($specials); - - -//echo $specials->getSelect(); + $specials->setVisibility(Mage::getSingleton('catalog/product_visibility')->getVisibleInCatalogIds()); $newurl = Mage::getUrl('rss/catalog/new'); $title = Mage::helper('rss')->__('%s - Special Discounts', Mage::app()->getStore()->getName()); @@ -112,33 +104,40 @@ protected function _toHtml() ->walk($specials->getSelect(), array(array($this, 'addSpecialXmlCallback')), array('rssObj'=> $rssObj, 'results'=> &$results)); if(sizeof($results)>0){ - usort($results, array(&$this, 'sortByStartDate')); - foreach($results as $result){ - $product->setData($result); - //$product->unsetData()->load($result['entity_id']); - - $special_price = ($result['use_special'] ? $result['special_price'] : $result['rule_price']); - $description = ''. - ''. - ''.$product->getDescription(). - ' Price:'.Mage::helper('core')->currency($product->getPrice()). - ' Special Price:'. Mage::helper('core')->currency($special_price). - ($result['use_special'] && $result['special_to_date'] ? ' Special Expires on: '.$this->formatDate($result['special_to_date'], Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM) : ''). - ''. - ''. - ''; + usort($results, array(&$this, 'sortByStartDate')); + foreach($results as $result){ + $product->setData($result); + + $description = ''. + ''. + ''.$product->getDescription(); + + if ($product->getAllowedPriceInRss()) { + $specialPrice = ($result['use_special'] ? $result['special_price'] : $result['rule_price']); + $description .= ' Price:'.Mage::helper('core')->currency($product->getPrice()). + ' Special Price:'. Mage::helper('core')->currency($specialPrice). + ($result['use_special'] && $result['special_to_date'] ? ' Special Expires on: '.$this->formatDate($result['special_to_date'], Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM) : ''). + ''; + } + + $description .= ''; + $data = array( 'title' => $product->getName(), 'link' => $product->getProductUrl(), - 'description' => $description, - - ); + 'description' => $description + ); $rssObj->_addEntry($data); - } + } } return $rssObj->createRssXml(); } + /** + * Preparing data and adding to rss object + * + * @param array $args + */ public function addSpecialXmlCallback($args) { /* @@ -156,17 +155,21 @@ public function addSpecialXmlCallback($args) return; } -//echo ""; -//print_r($args['row']); $row = $args['row']; - $special_price = $row['special_price']; - $rule_price = $row['rule_price']; - if (!$rule_price || ($rule_price && $special_price && $special_price<=$rule_price)) { - $row['start_date'] = $row['special_from_date']; - $row['use_special'] = true; + + if ($product->getAllowedPriceInRss()) { + $specialPrice = $row['special_price']; + $rule_price = $row['rule_price']; + if (!$rulePrice || ($rulePrice && $specialPrice && $specialPrice<=$rulePrice)) { + $row['start_date'] = $row['special_from_date']; + $row['use_special'] = true; + } else { + $row['start_date'] = $row['rule_start_date']; + $row['use_special'] = false; + } + $row['allowed_price_in_rss'] = true; } else { - $row['start_date'] = $row['rule_start_date']; - $row['use_special'] = false; + $row['allowed_price_in_rss'] = false; } $args['results'][] = $row; } diff --git a/app/code/core/Mage/Rss/Block/Catalog/Tag.php b/app/code/core/Mage/Rss/Block/Catalog/Tag.php index 1bf0b34726..be79fdcaad 100644 --- a/app/code/core/Mage/Rss/Block/Catalog/Tag.php +++ b/app/code/core/Mage/Rss/Block/Catalog/Tag.php @@ -64,6 +64,8 @@ protected function _toHtml() ->addTagFilter($tagModel->getId()) ->addStoreFilter($storeId); + $_collection->setVisibility(Mage::getSingleton('catalog/product_visibility')->getVisibleInCatalogIds()); + $product = Mage::getModel('catalog/product'); Mage::getSingleton('core/resource_iterator') @@ -72,6 +74,11 @@ protected function _toHtml() return $rssObj->createRssXml(); } + /** + * Preparing data and adding to rss object + * + * @param array $args + */ public function addTaggedItemXml($args) { $product = $args['product']; @@ -84,20 +91,25 @@ public function addTaggedItemXml($args) return; } + $allowedPriceInRss = $product->getAllowedPriceInRss(); + $product->unsetData()->load($args['row']['entity_id']); $description = ''. ''. - ''.$product->getDescription(). - ' Price:'.Mage::helper('core')->currency($product->getFinalPrice()).''. - ''. - ''; + ''.$product->getDescription(); + + if ($allowedPriceInRss) { + $description .= ' Price:'.Mage::helper('core')->currency($product->getFinalPrice()).''; + } + + $description .=''; + $rssObj = $args['rssObj']; $data = array( 'title' => $product->getName(), 'link' => $product->getProductUrl(), 'description' => $description, - - ); + ); $rssObj->_addEntry($data); } } \ No newline at end of file diff --git a/app/code/core/Mage/Rss/Block/Order/New.php b/app/code/core/Mage/Rss/Block/Order/New.php index 310ed82b71..d2c34fd2ad 100644 --- a/app/code/core/Mage/Rss/Block/Order/New.php +++ b/app/code/core/Mage/Rss/Block/Order/New.php @@ -29,12 +29,21 @@ * * @category Mage * @package Mage_Rss - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Rss_Block_Order_New extends Mage_Core_Block_Template { + + /** + * Cache tag constant for feed new orders + * + * @var string + */ + const CACHE_TAG = 'block_html_rss_order_new'; + protected function _construct() { + $this->setCacheTags(array(self::CACHE_TAG)); /* * setting cache to save the rss for 10 minutes */ @@ -64,6 +73,9 @@ protected function _toHtml() ; $detailBlock = Mage::getBlockSingleton('rss/order_details'); + + Mage::dispatchEvent('rss_order_new_collection_select', array('collection' => $collection)); + Mage::getSingleton('core/resource_iterator') ->walk($collection->getSelect(), array(array($this, 'addNewOrderXmlCallback')), array('rssObj'=> $rssObj, 'order'=>$order , 'detailBlock' => $detailBlock)); @@ -88,4 +100,4 @@ public function addNewOrderXmlCallback($args) $rssObj->_addEntry($data); } } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Rss/Helper/Catalog.php b/app/code/core/Mage/Rss/Helper/Catalog.php index 0cf86e41a3..db227f6c59 100644 --- a/app/code/core/Mage/Rss/Helper/Catalog.php +++ b/app/code/core/Mage/Rss/Helper/Catalog.php @@ -38,7 +38,7 @@ public function getTagFeedUrl() if(Mage::getStoreConfig('rss/catalog/tag') && $this->_getRequest()->getParam('tagId')){ $tagModel = Mage::getModel('tag/tag')->load($this->_getRequest()->getParam('tagId')); if($tagModel && $tagModel->getId()){ - return Mage::getUrl('rss/catalog/tag', array('tagName' => $tagModel->getName())); + return Mage::getUrl('rss/catalog/tag', array('tagName' => urlencode($tagModel->getName()))); } } return $url; diff --git a/app/code/core/Mage/Rss/Helper/Data.php b/app/code/core/Mage/Rss/Helper/Data.php index 8e21ff758a..077c877328 100644 --- a/app/code/core/Mage/Rss/Helper/Data.php +++ b/app/code/core/Mage/Rss/Helper/Data.php @@ -66,7 +66,7 @@ public function authAdmin($path) } list($username, $password) = $this->authValidate(); Mage::getSingleton('adminhtml/url')->setNoSecret(true); - $adminSession = Mage::getModel('admin/session'); + $adminSession = Mage::getSingleton('admin/session'); $user = $adminSession->login($username, $password); //$user = Mage::getModel('admin/user')->login($username, $password); if($user && $user->getId() && $user->getIsActive() == '1' && $adminSession->isAllowed($path)){ @@ -96,4 +96,4 @@ public function authFailed() { Mage::helper('core/http')->authFailed(); } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Rss/Model/Observer.php b/app/code/core/Mage/Rss/Model/Observer.php new file mode 100644 index 0000000000..b7f4e84b58 --- /dev/null +++ b/app/code/core/Mage/Rss/Model/Observer.php @@ -0,0 +1,76 @@ + + */ +class Mage_Rss_Model_Observer +{ + + /** + * Clean cache for catalog review rss + * + * @param Varien_Event_Observer $observer + * @return void + */ + public function reviewSaveAfter(Varien_Event_Observer $observer) + { + + Mage::app()->cleanCache(array(Mage_Rss_Block_Catalog_Review::CACHE_TAG)); + + } + + /** + * Clean cache for notify stock rss + * + * @param Varien_Event_Observer $observer + * @return void + */ + public function salesOrderItemSaveAfterNotifyStock(Varien_Event_Observer $observer) + { + + Mage::app()->cleanCache(array(Mage_Rss_Block_Catalog_NotifyStock::CACHE_TAG)); + + } + + /** + * Clean cache for catalog new orders rss + * + * @param Varien_Event_Observer $observer + * @return void + */ + public function salesOrderItemSaveAfterOrderNew(Varien_Event_Observer $observer) + { + + Mage::app()->cleanCache(array(Mage_Rss_Block_Order_New::CACHE_TAG)); + + } +} diff --git a/app/code/core/Mage/Rss/controllers/CatalogController.php b/app/code/core/Mage/Rss/controllers/CatalogController.php index aaaef06157..f062a6cd9d 100644 --- a/app/code/core/Mage/Rss/controllers/CatalogController.php +++ b/app/code/core/Mage/Rss/controllers/CatalogController.php @@ -76,7 +76,7 @@ public function salesruleAction() public function tagAction() { if ($this->checkFeedEnable('tag')) { - $tagName = $this->getRequest()->getParam('tagName'); + $tagName = urldecode($this->getRequest()->getParam('tagName')); $tagModel = Mage::getModel('tag/tag'); $tagModel->loadByName($tagName); if ($tagModel->getId() && $tagModel->getStatus()==$tagModel->getApprovedStatus()) { diff --git a/app/code/core/Mage/Rss/controllers/OrderController.php b/app/code/core/Mage/Rss/controllers/OrderController.php index c2bf035c85..3ba14169c1 100644 --- a/app/code/core/Mage/Rss/controllers/OrderController.php +++ b/app/code/core/Mage/Rss/controllers/OrderController.php @@ -36,7 +36,6 @@ class Mage_Rss_OrderController extends Mage_Core_Controller_Front_Action { public function newAction() { - Mage::helper('rss')->authAdmin('sales/order'); $this->getResponse()->setHeader('Content-type', 'text/xml; charset=UTF-8'); $this->loadLayout(false); $this->renderLayout(); @@ -70,4 +69,18 @@ public function statusAction() } $this->_forward('nofeed', 'index', 'rss'); } -} \ No newline at end of file + + /** + * Controller predispatch method to change area for some specific action. + * + * @return Mage_Rss_OrderController + */ + public function preDispatch() + { + if ($this->getRequest()->getActionName() == 'new') { + $this->_currentArea = 'adminhtml'; + Mage::helper('rss')->authAdmin('sales/order'); + } + parent::preDispatch(); + } +} diff --git a/app/code/core/Mage/Rss/etc/adminhtml.xml b/app/code/core/Mage/Rss/etc/adminhtml.xml new file mode 100644 index 0000000000..6c85ddc622 --- /dev/null +++ b/app/code/core/Mage/Rss/etc/adminhtml.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + Rss Feeds Section + 135 + + + + + + + + + + diff --git a/app/code/core/Mage/Rss/etc/config.xml b/app/code/core/Mage/Rss/etc/config.xml index f03007300c..d475a5db8b 100644 --- a/app/code/core/Mage/Rss/etc/config.xml +++ b/app/code/core/Mage/Rss/etc/config.xml @@ -75,28 +75,26 @@ - - - - - - - - - - Rss Feeds Section - 135 - - - - - - - - - + + + + + rss/observer + salesOrderItemSaveAfterNotifyStock + + + + + + + rss/observer + salesOrderItemSaveAfterOrderNew + + + + diff --git a/app/code/core/Mage/Rule/Block/Editable.php b/app/code/core/Mage/Rule/Block/Editable.php index 31171c7830..b3be739ba0 100644 --- a/app/code/core/Mage/Rule/Block/Editable.php +++ b/app/code/core/Mage/Rule/Block/Editable.php @@ -28,36 +28,37 @@ class Mage_Rule_Block_Editable extends Mage_Core_Block_Abstract implements Varien_Data_Form_Element_Renderer_Interface { - public function render(Varien_Data_Form_Element_Abstract $element) - { - $valueName = $element->getValueName(); + public function render(Varien_Data_Form_Element_Abstract $element) + { + $element->addClass('element-value-changer'); + $valueName = $element->getValueName(); - if ($valueName==='') { - $valueName = '...'; - } else { - $valueName = Mage::helper('core/string')->truncate($valueName, 30); - } - if ($element->getShowAsText()) { - $html = ' '; + if ($valueName==='') { + $valueName = '...'; + } else { + $valueName = Mage::helper('core/string')->truncate($valueName, 30); + } + if ($element->getShowAsText()) { + $html = ' '; - $html.= htmlspecialchars($valueName).' '; - } else { - $html = ' getParamId() ? ' id="' . $element->getParamId() . '"' : '') . '>'; + $html.= htmlspecialchars($valueName).' '; + } else { + $html = ' getParamId() ? ' id="' . $element->getParamId() . '"' : '') . '>'; - $html.= ''; + $html.= ''; - $html.= htmlspecialchars($valueName); + $html.= htmlspecialchars($valueName); - $html.= ' '; + $html.= ' '; - $html.= $element->getElementHtml(); + $html.= $element->getElementHtml(); - if ($element->getExplicitApply()) { - $html.= ' '; - } + if ($element->getExplicitApply()) { + $html.= ' '; + } - $html.= ' '; - } - return $html; - } + $html.= ' '; + } + return $html; + } } \ No newline at end of file diff --git a/app/code/core/Mage/Rule/Block/Newchild.php b/app/code/core/Mage/Rule/Block/Newchild.php index fbcbd8d3b7..180054a21d 100644 --- a/app/code/core/Mage/Rule/Block/Newchild.php +++ b/app/code/core/Mage/Rule/Block/Newchild.php @@ -28,14 +28,15 @@ class Mage_Rule_Block_Newchild extends Mage_Core_Block_Abstract implements Varien_Data_Form_Element_Renderer_Interface { - public function render(Varien_Data_Form_Element_Abstract $element) - { - $html = ' getParamId() ? ' id="' . $element->getParamId() . '"' : '') . '>'; - $html.= ''; - $html.= $element->getValueName(); - $html.= ''; - $html.= $element->getElementHtml(); - $html.= ' '; - return $html; - } + public function render(Varien_Data_Form_Element_Abstract $element) + { + $element->addClass('element-value-changer'); + $html = ' getParamId() ? ' id="' . $element->getParamId() . '"' : '') . '>'; + $html.= ''; + $html.= $element->getValueName(); + $html.= ''; + $html.= $element->getElementHtml(); + $html.= ' '; + return $html; + } } \ No newline at end of file diff --git a/app/code/core/Mage/Rule/Model/Condition/Abstract.php b/app/code/core/Mage/Rule/Model/Condition/Abstract.php index 915d0bfbba..065f3133ad 100644 --- a/app/code/core/Mage/Rule/Model/Condition/Abstract.php +++ b/app/code/core/Mage/Rule/Model/Condition/Abstract.php @@ -33,10 +33,50 @@ abstract class Mage_Rule_Model_Condition_Abstract extends Varien_Object implements Mage_Rule_Model_Condition_Interface { + /** + * Defines which operators will be available for this condition + * + * @var string + */ + protected $_inputType = null; + + /** + * Default values for possible operator options + * @var array + */ + protected $_defaultOperatorOptions; + + /** + * Default combinations of operator options, depending on input type + * @var array + */ + protected $_defaultOperatorInputByType = array( + 'string' => array('==', '!=', '>=', '>', '<=', '<', '{}', '!{}', '()', '!()'), + 'numeric' => array('==', '!=', '>=', '>', '<=', '<', '()', '!()'), + 'date' => array('==', '>=', '<='), + 'select' => array('==', '!='), + 'multiselect' => array('==', '!=', '{}', '!{}'), + 'grid' => array('()', '!()'), + ); + public function __construct() { parent::__construct(); + $this->_defaultOperatorOptions = array( + '==' => Mage::helper('rule')->__('is'), + '!=' => Mage::helper('rule')->__('is not'), + '>=' => Mage::helper('rule')->__('equals or greater than'), + '<=' => Mage::helper('rule')->__('equals or less than'), + '>' => Mage::helper('rule')->__('greater than'), + '<' => Mage::helper('rule')->__('less than'), + '{}' => Mage::helper('rule')->__('contains'), + '!{}' => Mage::helper('rule')->__('does not contain'), + '!{}' => Mage::helper('rule')->__('does not contain'), + '()' => Mage::helper('rule')->__('is one of'), + '!()' => Mage::helper('rule')->__('is not one of'), + ); + $this->loadAttributeOptions()->loadOperatorOptions()->loadValueOptions(); if ($options = $this->getAttributeOptions()) { @@ -123,39 +163,24 @@ public function getAttributeName() public function loadOperatorOptions() { - $this->setOperatorOption(array( - '==' => Mage::helper('rule')->__('is'), - '!=' => Mage::helper('rule')->__('is not'), - '>=' => Mage::helper('rule')->__('equals or greater than'), - '<=' => Mage::helper('rule')->__('equals or less than'), - '>' => Mage::helper('rule')->__('greater than'), - '<' => Mage::helper('rule')->__('less than'), - '{}' => Mage::helper('rule')->__('contains'), - '!{}' => Mage::helper('rule')->__('does not contain'), - '()' => Mage::helper('rule')->__('is one of'), - '!()' => Mage::helper('rule')->__('is not one of'), - )); - $this->setOperatorByInputType(array( - 'string' => array('==', '!=', '>=', '>', '<=', '<', '{}', '!{}', '()', '!()'), - 'numeric' => array('==', '!=', '>=', '>', '<=', '<', '()', '!()'), - 'date' => array('==', '>=', '<='), - 'select' => array('==', '!='), - 'multiselect' => array('==', '!=', '{}', '!{}'), - 'grid' => array('()', '!()'), - )); + $this->setOperatorOption($this->_defaultOperatorOptions); + $this->setOperatorByInputType($this->_defaultOperatorInputByType); return $this; } /** * This value will define which operators will be available for this condition. * - * Possible values are: string, numeric, date, select, multiselect, grid + * Possible values are: string, numeric, date, select, multiselect, grid, boolean * * @return string */ public function getInputType() { - return 'string'; + if (null === $this->_inputType) { + return 'string'; + } + return $this->_inputType; } public function getOperatorSelectOptions() diff --git a/app/code/core/Mage/Rule/Model/Condition/Combine.php b/app/code/core/Mage/Rule/Model/Condition/Combine.php index ead83c1721..4e1f7845c8 100644 --- a/app/code/core/Mage/Rule/Model/Condition/Combine.php +++ b/app/code/core/Mage/Rule/Model/Condition/Combine.php @@ -157,8 +157,10 @@ public function asXml($containerKey='conditions', $itemKey='condition') public function loadArray($arr, $key='conditions') { - $this->setAggregator(isset($arr['aggregator']) ? $arr['aggregator'] : $arr['attribute']) - ->setValue(isset($arr['value']) ? $arr['value'] : $arr['operator']); + $this->setAggregator(isset($arr['aggregator']) ? $arr['aggregator'] + : (isset($arr['attribute']) ? $arr['attribute'] : null)) + ->setValue(isset($arr['value']) ? $arr['value'] + : (isset($arr['operator']) ? $arr['operator'] : null)); if (!empty($arr[$key]) && is_array($arr[$key])) { foreach ($arr[$key] as $condArr) { @@ -169,7 +171,7 @@ public function loadArray($arr, $key='conditions') $cond->loadArray($condArr, $key); } } catch (Exception $e) { - + Mage::logException($e); } } } diff --git a/app/code/core/Mage/Rule/Model/Rule.php b/app/code/core/Mage/Rule/Model/Rule.php index 4ddb5a6014..0eda1c8516 100644 --- a/app/code/core/Mage/Rule/Model/Rule.php +++ b/app/code/core/Mage/Rule/Model/Rule.php @@ -44,7 +44,11 @@ class Mage_Rule_Model_Rule extends Mage_Core_Model_Abstract */ protected $_isReadonly = false; - + /** + * Init resoirce + * + * @return unknown_type + */ protected function _construct() { $this->_init('rule/rule'); diff --git a/app/code/core/Mage/Sales/Block/Order/Totals.php b/app/code/core/Mage/Sales/Block/Order/Totals.php index 3f55e98590..5196fbb429 100644 --- a/app/code/core/Mage/Sales/Block/Order/Totals.php +++ b/app/code/core/Mage/Sales/Block/Order/Totals.php @@ -104,7 +104,7 @@ protected function _initTotals() /** * Add shipping */ - if ($this->getSource()->getShippingAmount() || $this->getSource()->getShippingDescription()) + if (!$this->getSource()->getIsVirtual() && ((float) $this->getSource()->getShippingAmount() || $this->getSource()->getShippingDescription())) { $this->_totals['shipping'] = new Varien_Object(array( 'code' => 'shipping', @@ -211,7 +211,7 @@ public function addTotalBefore(Varien_Object $total, $before=null) } $this->_totals = $totals; return $this; - } + } } $totals = array(); $first = array_shift($this->_totals); @@ -283,7 +283,7 @@ public function getTotals($area=null) $totals[] = $total; } } - } + } return $totals; } diff --git a/app/code/core/Mage/Sales/Block/Reorder/Sidebar.php b/app/code/core/Mage/Sales/Block/Reorder/Sidebar.php index 6d658debb7..89ef543317 100644 --- a/app/code/core/Mage/Sales/Block/Reorder/Sidebar.php +++ b/app/code/core/Mage/Sales/Block/Reorder/Sidebar.php @@ -53,6 +53,17 @@ public function __construct() } } + /** + * Retrieve form action url and set "secure" param to avoid confirm + * message when we submit form from secure page to unsecure + * + * @return string + */ + public function getFormActionUrl() + { + return $this->getUrl('checkout/cart/addgroup', array('_secure' => true)); + } + public function getLastOrder() { foreach ($this->getOrders() as $order) { diff --git a/app/code/core/Mage/Sales/Model/Mysql4/Order/Collection.php b/app/code/core/Mage/Sales/Model/Mysql4/Order/Collection.php index 8165cb11b4..72753173a5 100644 --- a/app/code/core/Mage/Sales/Model/Mysql4/Order/Collection.php +++ b/app/code/core/Mage/Sales/Model/Mysql4/Order/Collection.php @@ -73,20 +73,15 @@ public function getSelectCountSql() } /** - * Retrive all ids for collection + * Reset left join * - * @return array + * @return Mage_Eav_Model_Entity_Collection_Abstract */ - public function getAllIds($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->from(null, 'e.'.$this->getEntity()->getIdFieldName()); - $idsSelect->limit($limit, $offset); + $idsSelect = parent::_getAllIdsSelect($limit, $offset); $idsSelect->resetJoinLeft(); - return $this->getConnection()->fetchCol($idsSelect, $this->_bindParams); + return $idsSelect; } + } diff --git a/app/code/core/Mage/Sales/Model/Mysql4/Order/Invoice/Collection.php b/app/code/core/Mage/Sales/Model/Mysql4/Order/Invoice/Collection.php index 0bb13eae8c..325c3d5eec 100644 --- a/app/code/core/Mage/Sales/Model/Mysql4/Order/Invoice/Collection.php +++ b/app/code/core/Mage/Sales/Model/Mysql4/Order/Invoice/Collection.php @@ -34,21 +34,40 @@ class Mage_Sales_Model_Mysql4_Order_Invoice_Collection extends Mage_Eav_Model_Entity_Collection_Abstract { + /** + * Initialize orders collection + * + */ protected function _construct() { $this->_init('sales/order_invoice'); } + /** + * Add order filter + * + * @return Mage_Sales_Model_Mysql4_Order_Invoice_Collection + */ public function setOrderFilter($order) { if ($order instanceof Mage_Sales_Model_Order) { $this->addAttributeToFilter('order_id', $order->getId()); - } - else { + } else { $this->addAttributeToFilter('order_id', $order); } - return $this; } + /** + * Reset left join + * + * @return Mage_Eav_Model_Entity_Collection_Abstract + */ + protected function _getAllIdsSelect($limit = null, $offset = null) + { + $idsSelect = parent::_getAllIdsSelect($limit, $offset); + $idsSelect->resetJoinLeft(); + return $idsSelect; + } + } diff --git a/app/code/core/Mage/Sales/Model/Mysql4/Setup.php b/app/code/core/Mage/Sales/Model/Mysql4/Setup.php index 25fa721aac..fd92965ae4 100644 --- a/app/code/core/Mage/Sales/Model/Mysql4/Setup.php +++ b/app/code/core/Mage/Sales/Model/Mysql4/Setup.php @@ -420,6 +420,8 @@ public function getDefaultEntities() 'base_shipping_canceled' => array('type'=>'static'), 'base_shipping_invoiced' => array('type'=>'static'), + 'protect_code' => array('type' => 'static'), + 'customer_id' => array('type'=>'static', 'visible'=>false), 'customer_group_id' => array('type'=>'int', 'visible'=>false), 'customer_email' => array('type'=>'varchar', 'visible'=>false), diff --git a/app/code/core/Mage/Sales/Model/Order.php b/app/code/core/Mage/Sales/Model/Order.php index 0b17f7a7a0..b61400258d 100644 --- a/app/code/core/Mage/Sales/Model/Order.php +++ b/app/code/core/Mage/Sales/Model/Order.php @@ -434,7 +434,6 @@ protected function _placePayment() $this->getPayment()->place(); return $this; } - /** * Retrieve order payment model object * @@ -570,7 +569,23 @@ public function addStatusToHistory($status, $comment='', $isCustomerNotified = f public function place() { Mage::dispatchEvent('sales_order_place_before', array('order'=>$this)); - $this->_placePayment(); + $this->setState(self::STATE_NEW, true)->save(); + try { + $this->_placePayment(); + } catch (Mage_Core_Exception $e){ + $message = $e->getMessage(); + Mage::logException($e); + $this->addStatusToHistory( + $this->getStatus(), + Mage::helper('sales')->__('Payment failed: %s', $message) + )->save(); + } catch (Exception $e){ + Mage::logException($e); + $this->addStatusToHistory( + $this->getStatus(), + Mage::helper('sales')->__('Payment failed') + )->save(); + } Mage::dispatchEvent('sales_order_place_after', array('order'=>$this)); return $this; } @@ -1428,6 +1443,8 @@ protected function _beforeSave() } } + $this->setData('protect_code', substr(md5(uniqid(mt_rand(), true) . ':' . microtime(true)), 5, 6)); + return $this; } diff --git a/app/code/core/Mage/Sales/Model/Order/Creditmemo.php b/app/code/core/Mage/Sales/Model/Order/Creditmemo.php index 53214bd9b6..4b3a2b2137 100644 --- a/app/code/core/Mage/Sales/Model/Order/Creditmemo.php +++ b/app/code/core/Mage/Sales/Model/Order/Creditmemo.php @@ -412,6 +412,10 @@ public function register() ); } + $this->getOrder()->setBaseTotalInvoicedCost( + $this->getOrder()->getBaseTotalInvoicedCost()-$this->getBaseCost() + ); + $state = $this->getState(); if (is_null($state)) { $this->setState(self::STATE_OPEN); diff --git a/app/code/core/Mage/Sales/Model/Order/Creditmemo/Total/Cost.php b/app/code/core/Mage/Sales/Model/Order/Creditmemo/Total/Cost.php new file mode 100644 index 0000000000..8149f0d570 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Order/Creditmemo/Total/Cost.php @@ -0,0 +1,47 @@ +getAllItems() as $item) { + if (!$item->getHasChildren()){ + $baseRefundTotalCost += $item->getBaseCost()*$item->getQty(); + } + } + $creditmemo->setBaseCost($baseRefundTotalCost); + return $this; + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Sales/Model/Order/Invoice.php b/app/code/core/Mage/Sales/Model/Order/Invoice.php index c307ce1579..8c9acdda33 100644 --- a/app/code/core/Mage/Sales/Model/Order/Invoice.php +++ b/app/code/core/Mage/Sales/Model/Order/Invoice.php @@ -484,6 +484,9 @@ public function register() $this->getOrder()->setBaseDiscountInvoiced( $this->getOrder()->getBaseDiscountInvoiced()+$this->getBaseDiscountAmount() ); + $this->getOrder()->setBaseTotalInvoicedCost( + $this->getOrder()->getBaseTotalInvoicedCost()+$this->getBaseCost() + ); $state = $this->getState(); if (is_null($state)) { diff --git a/app/code/core/Mage/Sales/Model/Order/Invoice/Total/Cost.php b/app/code/core/Mage/Sales/Model/Order/Invoice/Total/Cost.php new file mode 100644 index 0000000000..e94a7bec84 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Order/Invoice/Total/Cost.php @@ -0,0 +1,47 @@ +getAllItems() as $item) { + if (!$item->getHasChildren()){ + $baseInvoiceTotalCost += $item->getBaseCost()*$item->getQty(); + } + } + $invoice->setBaseCost($baseInvoiceTotalCost); + return $this; + } +} \ No newline at end of file diff --git a/app/code/core/Mage/Sales/Model/Order/Shipment.php b/app/code/core/Mage/Sales/Model/Order/Shipment.php index 30efefb937..4cf2ac59dc 100644 --- a/app/code/core/Mage/Sales/Model/Order/Shipment.php +++ b/app/code/core/Mage/Sales/Model/Order/Shipment.php @@ -92,6 +92,17 @@ public function setOrder(Mage_Sales_Model_Order $order) return $this; } + + /** + * Retrieve hash code of current order + * + * @return string + */ + public function getProtectCode() + { + return (string)$this->getOrder()->getProtectCode(); + } + /** * Retrieve the order the shipment for created for * diff --git a/app/code/core/Mage/Sales/Model/Order/Shipment/Track.php b/app/code/core/Mage/Sales/Model/Order/Shipment/Track.php index a040f8eb08..f00507e7cf 100644 --- a/app/code/core/Mage/Sales/Model/Order/Shipment/Track.php +++ b/app/code/core/Mage/Sales/Model/Order/Shipment/Track.php @@ -71,6 +71,16 @@ public function isCustom() return $this->getCarrierCode() == self::CUSTOM_CARRIER_CODE; } + /** + * Retrieve hash code of current order + * + * @return string + */ + public function getProtectCode() + { + return (string)$this->getShipment()->getProtectCode(); + } + /** * Retrieve detail for shipment track * diff --git a/app/code/core/Mage/Sales/Model/Quote.php b/app/code/core/Mage/Sales/Model/Quote.php index fd717ffee5..c1d28afc05 100644 --- a/app/code/core/Mage/Sales/Model/Quote.php +++ b/app/code/core/Mage/Sales/Model/Quote.php @@ -894,6 +894,12 @@ public function removePayment() */ public function collectTotals() { + /** + * Protect double totals collection + */ + if ($this->getTotalsCollectedFlag()) { + return $this; + } Mage::dispatchEvent( $this->_eventPrefix . '_collect_totals_before', array( @@ -966,6 +972,7 @@ public function collectTotals() ) ); + $this->setTotalsCollectedFlag(true); return $this; } @@ -984,14 +991,25 @@ public function getTotals() if ($this->isVirtual()) { return $this->getBillingAddress()->getTotals(); } - - $totals = $this->getShippingAddress()->getTotals(); - foreach ($this->getBillingAddress()->getTotals() as $code => $total) { - if (isset($totals[$code])) { - $totals[$code]->setValue($totals[$code]->getValue()+$total->getValue()); + + $totals = null; + // Going through all quote addresses and sum their totals + foreach ($this->getAddressesCollection() as $address) { + if ($address->isDeleted()) { + continue; } - else { - $totals[$code] = $total; + if (!$totals) { + $totals = $address->getTotals(); + } else { + foreach ($address->getTotals() as $code => $total) { + if (isset($totals[$code])) { + $totals[$code]->setValue($totals[$code]->getValue()+$total->getValue()); + $totals[$code]->setValueExclTax($totals[$code]->getValueExclTax()+$total->getValueExclTax()); + $totals[$code]->setValueInclTax($totals[$code]->getValueInclTax()+$total->getValueInclTax()); + } else { + $totals[$code] = $total; + } + } } } diff --git a/app/code/core/Mage/Sales/Model/Quote/Address.php b/app/code/core/Mage/Sales/Model/Quote/Address.php index fc53b6d161..8bb04bbd9d 100644 --- a/app/code/core/Mage/Sales/Model/Quote/Address.php +++ b/app/code/core/Mage/Sales/Model/Quote/Address.php @@ -265,8 +265,7 @@ public function getAllItems() if ($this->getAddressType() == self::TYPE_BILLING) { $items[] = $qItem; } - } - else { + } else { if ($this->getAddressType() == self::TYPE_SHIPPING) { $items[] = $qItem; } @@ -568,9 +567,10 @@ public function collectShippingRates() $this->removeAllShippingRates(); - $havingOptionalZip = Mage::helper('directory')->getCountriesWithOptionalZip(); - $postcodeValid = $this->getPostcode() || in_array($this->getCountryId(), $havingOptionalZip); - if (!$this->getCountryId() && !$postcodeValid) { + $countryId = $this->getCountryId(); + $postCode = $this->getPostcode(); + if ((!$countryId && !$postCode) + || ($countryId && !$postCode && !Mage::helper('directory')->isZipCodeOptional($countryId))) { return $this; } @@ -844,6 +844,34 @@ public function setBaseTotalAmount($code, $amount) return $this; } + /** + * Add amount total amount value + * + * @param string $code + * @param float $amount + * @return Mage_Sales_Model_Quote_Address + */ + public function addTotalAmount($code, $amount) + { + $amount = $this->getTotalAmount($code)+$amount; + $this->setTotalAmount($code, $amount); + return $this; + } + + /** + * Add amount total amount value in base store currency + * + * @param string $code + * @param float $amount + * @return Mage_Sales_Model_Quote_Address + */ + public function addBaseTotalAmount($code, $amount) + { + $amount = $this->getBaseTotalAmount($code)+$amount; + $this->setBaseTotalAmount($code, $amount); + return $this; + } + /** * Get total amount value by code * diff --git a/app/code/core/Mage/Sales/Model/Quote/Address/Total/Abstract.php b/app/code/core/Mage/Sales/Model/Quote/Address/Total/Abstract.php index 7336a398a2..95fd306e06 100644 --- a/app/code/core/Mage/Sales/Model/Quote/Address/Total/Abstract.php +++ b/app/code/core/Mage/Sales/Model/Quote/Address/Total/Abstract.php @@ -153,10 +153,7 @@ protected function _setBaseAmount($baseAmount) */ protected function _addAmount($amount) { - $this->_getAddress()->setTotalAmount( - $this->getCode(), - $this->_getAddress()->getTotalAmount($this->getCode())+$amount - ); + $this->_getAddress()->addTotalAmount($this->getCode(),$amount); return $this; } @@ -168,10 +165,7 @@ protected function _addAmount($amount) */ protected function _addBaseAmount($baseAmount) { - $this->_getAddress()->setBaseTotalAmount( - $this->getCode(), - $this->_getAddress()->getBaseTotalAmount($this->getCode())+$baseAmount - ); + $this->_getAddress()->addBaseTotalAmount($this->getCode(), $baseAmount); return $this; } diff --git a/app/code/core/Mage/Sales/Model/Quote/Item.php b/app/code/core/Mage/Sales/Model/Quote/Item.php index 9f9f227496..6091f7c26b 100644 --- a/app/code/core/Mage/Sales/Model/Quote/Item.php +++ b/app/code/core/Mage/Sales/Model/Quote/Item.php @@ -215,6 +215,23 @@ public function getQtyOptions() return $return; } + /** + * Checking item data + * + * @return Mage_Sales_Model_Quote_Item_Abstract + */ + public function checkData() + { + $parent = parent::checkData(); + if ($this->getProduct()->getHasError()) { + $this->setHasError(true); + $this->setMessage(Mage::helper('sales')->__('Item options declare error')); + $this->getQuote()->setHasError(true); + $this->getQuote()->addMessage($this->getProduct()->getMessage(), 'options'); + } + return $parent; + } + /** * Setup product for quote item * @@ -233,8 +250,10 @@ public function setProduct($product) ->setName($product->getName()) ->setWeight($this->getProduct()->getWeight()) ->setTaxClassId($product->getTaxClassId()) - ->setCost($product->getCost()) - ->setIsQtyDecimal($product->getIsQtyDecimal()); + ->setBaseCost($product->getCost()); + if ($product->getStockItem()) { + $this->setIsQtyDecimal($product->getStockItem()->getIsQtyDecimal()); + } Mage::dispatchEvent('sales_quote_item_set_product', array( 'product' => $product, @@ -261,6 +280,7 @@ public function getProduct() $product = Mage::getModel('catalog/product') ->setStoreId($this->getQuote()->getStoreId()) ->load($this->getProductId()); + $this->setProduct($product); } diff --git a/app/code/core/Mage/Sales/etc/adminhtml.xml b/app/code/core/Mage/Sales/etc/adminhtml.xml new file mode 100644 index 0000000000..486995d457 --- /dev/null +++ b/app/code/core/Mage/Sales/etc/adminhtml.xml @@ -0,0 +1,127 @@ + + + + + + Sales + 20 + Mage_Sales + + + Orders + adminhtml/sales_order + 10 + + + Invoices + adminhtml/sales_invoice + 20 + + + Shipments + adminhtml/sales_shipment + 30 + + + Credit Memos + adminhtml/sales_creditmemo + 40 + + + + + + + + + + Sales + + + Orders + + + Actions + + Create + View + Reorder + Edit + Cancel + Capture + Invoice + Creditmemo + Hold + Unhold + Ship + Comment + Reorder + + + + 10 + + + Invoices + 20 + + + Shipments + 30 + + + Credit Memos + 40 + + + + + + + + + Sales Section + 60 + + + Sales Emails Section + 65 + + + PDF Print-outs + 66 + + + + + + + + + + diff --git a/app/code/core/Mage/Sales/etc/config.xml b/app/code/core/Mage/Sales/etc/config.xml index d68e0d08b2..96f885efb2 100644 --- a/app/code/core/Mage/Sales/etc/config.xml +++ b/app/code/core/Mage/Sales/etc/config.xml @@ -28,7 +28,7 @@ - 0.9.40 + 0.9.44 @@ -73,6 +73,7 @@ * + * * * @@ -190,6 +191,7 @@ * * * + * @@ -294,7 +296,7 @@ * base_price** - cost* + *** @@ -465,14 +467,7 @@ Mage_Sales Mage_Sales_Model_Mysql4_Setup - core_setup - - core_write - - - core_read - Mage_Sales_Block @@ -520,13 +515,13 @@ + Pending - Pending PayPal Processing On Hold Complete @@ -537,48 +532,46 @@ New - + Pending Payment - - - + Processing - + Complete - + Closed - + Canceled - + On Hold - + @@ -601,6 +594,9 @@ sales/order_invoice_total_grand + + sales/order_invoice_total_cost + @@ -620,6 +616,9 @@ sales/order_creditmemo_total_grand + + sales/order_creditmemo_total_cost + @@ -714,104 +713,6 @@ - - - Sales - 20 - Mage_Sales - - - Orders - adminhtml/sales_order - 10 - - - Invoices - adminhtml/sales_invoice - 20 - - - Shipments - adminhtml/sales_shipment - 30 - - - Credit Memos - adminhtml/sales_creditmemo - 40 - - - - - - - - - - Sales - - - Orders - - - Actions - - Create - View - Reorder - Edit - Cancel - Capture - Invoice - Creditmemo - Hold - Unhold - Ship - Comment - Reorder - - - - 10 - - - Invoices - 20 - - - Shipments - 30 - - - Credit Memos - 40 - - - - - - - - - Sales Section - 60 - - - Sales Emails Section - 65 - - - PDF Print-outs - 66 - - - - - - - - - @@ -832,7 +733,6 @@ - singleton sales/observer substractQtyFromQuotes @@ -841,7 +741,6 @@ - singleton sales/observer markQuotesRecollectOnCatalogRules @@ -850,7 +749,6 @@ - singleton sales/observer markQuotesRecollectOnCatalogRules @@ -859,7 +757,6 @@ - singleton sales/observer catalogProductSaveAfter @@ -868,7 +765,6 @@ - singleton sales/observer catalogProductStatusUpdate diff --git a/app/code/core/Mage/Sales/sql/sales_setup/mysql4-install-0.9.0.php b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-install-0.9.0.php index 23d5e96c85..7778662198 100644 --- a/app/code/core/Mage/Sales/sql/sales_setup/mysql4-install-0.9.0.php +++ b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-install-0.9.0.php @@ -28,6 +28,7 @@ /* @var $installer Mage_Sales_Model_Entity_Setup */ $installer->startSetup(); $installer->run(" +DROP TABLE IF EXISTS `{$installer->getTable('sales_flat_quote')}`; CREATE TABLE `{$installer->getTable('sales_flat_quote')}` ( `entity_id` int(10) unsigned NOT NULL auto_increment, `store_id` smallint(5) unsigned NOT NULL default '0', @@ -79,7 +80,7 @@ KEY `IDX_CUSTOMER` (`customer_id`,`store_id`,`is_active`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; - +DROP TABLE IF EXISTS `{$installer->getTable('sales_flat_quote_address')}`; CREATE TABLE `{$installer->getTable('sales_flat_quote_address')}` ( `address_id` int(10) unsigned NOT NULL auto_increment, `quote_id` int(10) unsigned NOT NULL default '0', @@ -134,6 +135,7 @@ CONSTRAINT `FK_SALES_QUOTE_ADDRESS_SALES_QUOTE` FOREIGN KEY (`quote_id`) REFERENCES `{$installer->getTable('sales_flat_quote')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$installer->getTable('sales_flat_quote_address_item')}`; CREATE TABLE `{$installer->getTable('sales_flat_quote_address_item')}` ( `address_item_id` int(10) unsigned NOT NULL auto_increment, `quote_address_id` int(10) unsigned NOT NULL default '0', @@ -161,6 +163,7 @@ CONSTRAINT `FK_SALES_QUOTE_ADDRESS_ITEM_QUOTE_ITEM` FOREIGN KEY (`quote_item_id`) REFERENCES `{$installer->getTable('sales_flat_quote_item')}` (`item_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$installer->getTable('sales_flat_quote_item')}`; CREATE TABLE `{$installer->getTable('sales_flat_quote_item')}` ( `item_id` int(10) unsigned NOT NULL auto_increment, `quote_id` int(10) unsigned NOT NULL default '0', @@ -201,6 +204,7 @@ CONSTRAINT `FK_SALES_QUOTE_ITEM_SALES_QUOTE` FOREIGN KEY (`quote_id`) REFERENCES `{$installer->getTable('sales_flat_quote')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$installer->getTable('sales_flat_quote_item_option')}`; CREATE TABLE `{$installer->getTable('sales_flat_quote_item_option')}` ( `option_id` int(10) unsigned NOT NULL auto_increment, `item_id` int(10) unsigned NOT NULL, @@ -212,6 +216,7 @@ CONSTRAINT `FK_SALES_QUOTE_ITEM_OPTION_ITEM_ID` FOREIGN KEY (`item_id`) REFERENCES `{$installer->getTable('sales_flat_quote_item')}` (`item_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Additional options for quote item'; +DROP TABLE IF EXISTS `{$installer->getTable('sales_flat_quote_payment')}`; CREATE TABLE `{$installer->getTable('sales_flat_quote_payment')}` ( `payment_id` int(10) unsigned NOT NULL auto_increment, `quote_id` int(10) unsigned NOT NULL default '0', @@ -240,6 +245,7 @@ CONSTRAINT `FK_SALES_QUOTE_PAYMENT_SALES_QUOTE` FOREIGN KEY (`quote_id`) REFERENCES `{$installer->getTable('sales_flat_quote')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$installer->getTable('sales_flat_quote_shipping_rate')}`; CREATE TABLE `{$installer->getTable('sales_flat_quote_shipping_rate')}` ( `rate_id` int(10) unsigned NOT NULL auto_increment, `address_id` int(10) unsigned NOT NULL default '0', @@ -257,6 +263,7 @@ CONSTRAINT `FK_SALES_QUOTE_SHIPPING_RATE_ADDRESS` FOREIGN KEY (`address_id`) REFERENCES `{$installer->getTable('sales_flat_quote_address')}` (`address_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$installer->getTable('sales_order')}`; CREATE TABLE `{$installer->getTable('sales_order')}` ( `entity_id` int(10) unsigned NOT NULL auto_increment, `entity_type_id` smallint(5) unsigned NOT NULL default '0', @@ -320,6 +327,7 @@ CONSTRAINT `FK_SALE_ORDER_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$installer->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; +DROP TABLE IF EXISTS `{$installer->getTable('sales_order')}_datetime`; CREATE TABLE `{$this->getTable('sales_order')}_datetime` ( `value_id` int(11) NOT NULL auto_increment, `entity_type_id` smallint(5) unsigned NOT NULL default '0', @@ -335,6 +343,7 @@ CONSTRAINT `FK_SALES_ORDER_DATETIME_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$this->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$installer->getTable('sales_order')}_decimal`; CREATE TABLE `{$this->getTable('sales_order')}_decimal` ( `value_id` int(11) NOT NULL auto_increment, `entity_type_id` smallint(5) unsigned NOT NULL default '0', @@ -350,6 +359,7 @@ CONSTRAINT `FK_SALES_ORDER_DECIMAL_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$this->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$installer->getTable('sales_order')}_int`; CREATE TABLE `{$this->getTable('sales_order')}_int` ( `value_id` int(11) NOT NULL auto_increment, `entity_type_id` smallint(5) unsigned NOT NULL default '0', @@ -365,6 +375,7 @@ CONSTRAINT `FK_SALES_ORDER_INT_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$this->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$installer->getTable('sales_order')}_text`; CREATE TABLE `{$this->getTable('sales_order')}_text` ( `value_id` int(11) NOT NULL auto_increment, `entity_type_id` smallint(5) unsigned NOT NULL default '0', @@ -380,6 +391,7 @@ CONSTRAINT `FK_SALES_ORDER_TEXT_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$this->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$installer->getTable('sales_order')}_varchar`; CREATE TABLE `{$this->getTable('sales_order')}_varchar` ( `value_id` int(11) NOT NULL auto_increment, `entity_type_id` smallint(5) unsigned NOT NULL default '0', @@ -395,6 +407,7 @@ CONSTRAINT `FK_SALES_ORDER_VARCHAR_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$this->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$installer->getTable('sales_order_entity')}`; CREATE TABLE `{$installer->getTable('sales_order_entity')}` ( `entity_id` int(10) unsigned NOT NULL auto_increment, `entity_type_id` smallint(8) unsigned NOT NULL default '0', @@ -412,6 +425,7 @@ CONSTRAINT `FK_SALE_ORDER_ENTITY_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core_store')}` (`store_id`) ON DELETE SET NULL ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; +DROP TABLE IF EXISTS `{$this->getTable('sales_order_entity')}_datetime`; CREATE TABLE `{$this->getTable('sales_order_entity')}_datetime` ( `value_id` int(11) NOT NULL auto_increment, `entity_type_id` smallint(5) unsigned NOT NULL default '0', @@ -427,6 +441,7 @@ CONSTRAINT `FK_SALES_ORDER_ENTITY_DATETIME_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$this->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$this->getTable('sales_order_entity')}_decimal`; CREATE TABLE `{$installer->getTable('sales_order_entity')}_decimal` ( `value_id` int(11) NOT NULL auto_increment, `entity_type_id` smallint(5) unsigned NOT NULL default '0', @@ -442,6 +457,7 @@ CONSTRAINT `FK_SALES_ORDER_ENTITY_DECIMAL_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$this->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$this->getTable('sales_order_entity')}_int`; CREATE TABLE `{$installer->getTable('sales_order_entity')}_int` ( `value_id` int(11) NOT NULL auto_increment, `entity_type_id` smallint(5) unsigned NOT NULL default '0', @@ -457,6 +473,7 @@ CONSTRAINT `FK_SALES_ORDER_ENTITY_INT_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$this->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$this->getTable('sales_order_entity')}_text`; CREATE TABLE `{$installer->getTable('sales_order_entity')}_text` ( `value_id` int(11) NOT NULL auto_increment, `entity_type_id` smallint(5) unsigned NOT NULL default '0', @@ -472,6 +489,7 @@ CONSTRAINT `FK_SALES_ORDER_ENTITY_TEXT_ENTITY_TYPE` FOREIGN KEY (`entity_type_id`) REFERENCES `{$this->getTable('eav_entity_type')}` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{$this->getTable('sales_order_entity')}_varchar`; CREATE TABLE `{$installer->getTable('sales_order_entity')}_varchar` ( `value_id` int(11) NOT NULL auto_increment, `entity_type_id` smallint(5) unsigned NOT NULL default '0', diff --git a/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.40-0.9.41.php b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.40-0.9.41.php new file mode 100644 index 0000000000..9ce27392e2 --- /dev/null +++ b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.40-0.9.41.php @@ -0,0 +1,58 @@ +startSetup(); + +$installer->addAttribute('quote_item', 'base_cost', array( + 'type' => 'decimal', + 'label' => 'Cost', + 'visible' => false, + 'required' => false, +)); + +$installer->addAttribute('quote_address_item', 'base_cost', array( + 'type' => 'decimal', + 'label' => 'Cost', + 'visible' => false, + 'required' => false, +)); + +$installer->getConnection()->changeColumn($installer->getTable('sales/order_item'), 'cost', 'base_cost', 'DECIMAL( 12, 4 ) NULL DEFAULT \'0.0000\''); + +$installer->getConnection()->addColumn($installer->getTable('sales/order'), 'base_total_invoiced_cost', 'DECIMAL( 12, 4 ) NULL DEFAULT NULL'); + +$installer->addAttribute('order', 'base_total_invoiced_cost', array( + 'type' => 'static' +)); + +$installer->updateAttribute('order_item', 'cost', array('attribute_code' => 'base_cost')); +$installer->updateAttribute('invoice_item', 'cost', array('attribute_code' => 'base_cost')); +$installer->updateAttribute('creditmemo_item', 'cost', array('attribute_code' => 'base_cost')); + +$installer->endSetup(); \ No newline at end of file diff --git a/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.41-0.9.42.php b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.41-0.9.42.php new file mode 100644 index 0000000000..5709728575 --- /dev/null +++ b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.41-0.9.42.php @@ -0,0 +1,29 @@ +startSetup(); +$this->addAttribute('order', 'x_forwarded_for', array('type'=>'varchar')); +$this->endSetup(); \ No newline at end of file diff --git a/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.42-0.9.43.php b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.42-0.9.43.php new file mode 100644 index 0000000000..865f99dce5 --- /dev/null +++ b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.42-0.9.43.php @@ -0,0 +1,47 @@ +startSetup(); + +$orderAddressEntityTypeId = 'order_address'; +$attributeLabels = array( + 'firstname' => 'First Name', + 'lastname' => 'Last Name', + 'company' => 'Company', + 'street' => 'Street Address', + 'city' => 'City', + 'region_id' => 'State/Province', + 'postcode' => 'Zip/Postal Code', + 'country_id' => 'Country', + 'telephone' => 'Telephone', + 'email' => 'Email' +); + +foreach ($attributeLabels as $attributeCode => $attributeLabel){ + $this->updateAttribute($orderAddressEntityTypeId, $attributeCode, 'frontend_label', $attributeLabel); +} + +$this->endSetup(); \ No newline at end of file diff --git a/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.43-0.9.44.php b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.43-0.9.44.php new file mode 100644 index 0000000000..1fa6a94c27 --- /dev/null +++ b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-0.9.43-0.9.44.php @@ -0,0 +1,34 @@ +getConnection()->addColumn($installer->getTable('sales_order'), 'protect_code', 'VARCHAR( 6 ) NULL DEFAULT NULL'); + +$installer->addAttribute('order', 'protect_code', array('type'=>'static')); + +$installer->run("UPDATE `{$installer->getTable('sales_order')}` SET protect_code = SUBSTRING(MD5(CONCAT(RAND(), DATE_FORMAT(NOW(), '%H %k %I %r %T %S'), RAND())), 5, 6) WHERE protect_code IS NULL"); diff --git a/app/code/core/Mage/SalesRule/Helper/Data.php b/app/code/core/Mage/SalesRule/Helper/Data.php index 73d4d25248..bffe4f4cb3 100644 --- a/app/code/core/Mage/SalesRule/Helper/Data.php +++ b/app/code/core/Mage/SalesRule/Helper/Data.php @@ -29,5 +29,39 @@ */ class Mage_SalesRule_Helper_Data extends Mage_Core_Helper_Abstract { + /** + * Set store and base price which will be used duering discount calculation to item object + * + * @param Mage_Sales_Model_Quote_Item_Abstract $item + * @param float $basePrice + * @param float $price + * @return Mage_SalesRule_Helper_Data + */ + public function setItemDiscountPrices(Mage_Sales_Model_Quote_Item_Abstract $item, $basePrice, $price) + { + $item->setDiscountCalculationPrice($price); + $item->setBaseDiscountCalculationPrice($basePrice); + return $this; + } + /** + * Add additional amounts to discount calculation prices + * + * @param Mage_Sales_Model_Quote_Item_Abstract $item + * @param float $basePrice + * @param float $price + * @return Mage_SalesRule_Helper_Data + */ + public function addItemDiscountPrices(Mage_Sales_Model_Quote_Item_Abstract $item, $basePrice, $price) + { + $discountPrice = $item->getDiscountCalculationPrice(); + $baseDiscountPrice = $item->getBaseDiscountCalculationPrice(); + + if ($discountPrice || $baseDiscountPrice || $basePrice || $price) { + $discountPrice = $discountPrice ? $discountPrice : $item->getCalculationPrice(); + $baseDiscountPrice = $baseDiscountPrice ? $baseDiscountPrice : $item->getBaseCalculationPrice(); + $this->setItemDiscountPrices($item, $baseDiscountPrice+$basePrice, $discountPrice+$price); + } + return $this; + } } diff --git a/app/code/core/Mage/SalesRule/Model/Rule.php b/app/code/core/Mage/SalesRule/Model/Rule.php index 740ae254d5..b2fee2b90e 100644 --- a/app/code/core/Mage/SalesRule/Model/Rule.php +++ b/app/code/core/Mage/SalesRule/Model/Rule.php @@ -30,6 +30,22 @@ class Mage_SalesRule_Model_Rule extends Mage_Rule_Model_Rule const FREE_SHIPPING_ITEM = 1; const FREE_SHIPPING_ADDRESS = 2; + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'salesrule_rule'; + + /** + * Parameter name in event + * + * In observe method you can use $observer->getEvent()->getRule() in this case + * + * @var string + */ + protected $_eventObject = 'rule'; + protected $_labels = array(); protected function _construct() diff --git a/app/code/core/Mage/SalesRule/Model/Rule/Condition/Combine.php b/app/code/core/Mage/SalesRule/Model/Rule/Condition/Combine.php index dd8c91a057..aaa06e3899 100644 --- a/app/code/core/Mage/SalesRule/Model/Rule/Condition/Combine.php +++ b/app/code/core/Mage/SalesRule/Model/Rule/Condition/Combine.php @@ -49,6 +49,13 @@ public function getNewChildSelectOptions() array('value'=>'salesrule/rule_condition_combine', 'label'=>Mage::helper('salesrule')->__('Conditions combination')), array('label'=>Mage::helper('salesrule')->__('Cart Attribute'), 'value'=>$attributes), )); + + $additional = new Varien_Object(); + Mage::dispatchEvent('salesrule_rule_condition_combine', array('additional' => $additional)); + if ($additionalConditions = $additional->getConditions()) { + $conditions = array_merge_recursive($conditions, $additionalConditions); + } + return $conditions; } } diff --git a/app/code/core/Mage/SalesRule/Model/Rule/Condition/Product/Found.php b/app/code/core/Mage/SalesRule/Model/Rule/Condition/Product/Found.php index acf798b717..0e8130b35f 100644 --- a/app/code/core/Mage/SalesRule/Model/Rule/Condition/Product/Found.php +++ b/app/code/core/Mage/SalesRule/Model/Rule/Condition/Product/Found.php @@ -66,27 +66,24 @@ public function validate(Varien_Object $object) $true = (bool)$this->getValue(); $found = false; foreach ($object->getAllItems() as $item) { - $found = $all ? true : false; + $found = $all; foreach ($this->getConditions() as $cond) { $validated = $cond->validate($item); - if ($all && !$validated) { - $found = false; + if (($all && !$validated) || (!$all && $validated)) { + $found = $validated; break; - } elseif (!$all && $validated) { - $found = true; - break 2; } } - if ($found && $true) { + if (($found && $true) || (!$true && $found)) { break; } } + // found an item and we're looking for existing one if ($found && $true) { - // found an item and we're looking for existing one - return true; - } elseif (!$found && !$true) { - // not found and we're making sure it doesn't exist + } + // not found and we're making sure it doesn't exist + elseif (!$found && !$true) { return true; } return false; diff --git a/app/code/core/Mage/SalesRule/Model/Validator.php b/app/code/core/Mage/SalesRule/Model/Validator.php index 4f56703cca..8cc9bcd69b 100644 --- a/app/code/core/Mage/SalesRule/Model/Validator.php +++ b/app/code/core/Mage/SalesRule/Model/Validator.php @@ -167,7 +167,7 @@ public function processFreeShipping(Mage_Sales_Model_Quote_Item_Abstract $item) { $address = $this->_getAddress($item); $item->setFreeShipping(false); - + foreach ($this->_getRules() as $rule) { /* @var $rule Mage_SalesRule_Model_Rule */ if (!$this->_canProcessRule($rule, $address)) { @@ -221,7 +221,7 @@ public function process(Mage_Sales_Model_Quote_Item_Abstract $item) if (!$this->_canProcessRule($rule, $address)) { continue; } - + if (!$rule->getActions()->validate($item)) { continue; } @@ -274,8 +274,8 @@ public function process(Mage_Sales_Model_Quote_Item_Abstract $item) /** * We can't use row total here because row total not include tax */ - $discountAmount = min($itemPrice*$qty, $quoteAmount); - $baseDiscountAmount = min($baseItemPrice*$qty, $cartRules[$rule->getId()]); + $discountAmount = min($itemPrice*$qty - $item->getDiscountAmount(), $quoteAmount); + $baseDiscountAmount = min($baseItemPrice*$qty - $item->getBaseDiscountAmount(), $cartRules[$rule->getId()]); $cartRules[$rule->getId()] -= $baseDiscountAmount; } $address->setCartFixedRules($cartRules); @@ -284,7 +284,7 @@ public function process(Mage_Sales_Model_Quote_Item_Abstract $item) case 'buy_x_get_y': $x = $rule->getDiscountStep(); $y = $rule->getDiscountAmount(); - if (!$x || $y>=$x) { + if (!$x) { break; } $buy = 0; $free = 0; @@ -386,7 +386,7 @@ public function processShippingAmount(Mage_Sales_Model_Quote_Address $address) if (!$rule->getApplyToShipping() || !$this->_canProcessRule($rule, $address)) { continue; } - + $discountAmount = 0; $baseDiscountAmount = 0; $rulePercent = min(100, $rule->getDiscountAmount()); @@ -417,8 +417,8 @@ public function processShippingAmount(Mage_Sales_Model_Quote_Address $address) } if ($cartRules[$rule->getId()] > 0) { $quoteAmount = $quote->getStore()->convertPrice($cartRules[$rule->getId()]); - $discountAmount = min($shippingAmount, $quoteAmount); - $baseDiscountAmount = min($baseShippingAmount, $cartRules[$rule->getId()]); + $discountAmount = min($shippingAmount-$address->getShippingDiscountAmount(), $quoteAmount); + $baseDiscountAmount = min($baseShippingAmount-$address->getBaseShippingDiscountAmount(), $cartRules[$rule->getId()]); $cartRules[$rule->getId()] -= $baseDiscountAmount; } $address->setCartFixedRules($cartRules); @@ -476,7 +476,7 @@ protected function _addDiscountDescription($address, $rule) } elseif ($rule->getCouponCode()) { $label = $rule->getCouponCode(); } - + if (!empty($label)) { $description[$rule->getId()] = $label; } diff --git a/app/code/core/Mage/SalesRule/etc/adminhtml.xml b/app/code/core/Mage/SalesRule/etc/adminhtml.xml new file mode 100644 index 0000000000..38e8afa157 --- /dev/null +++ b/app/code/core/Mage/SalesRule/etc/adminhtml.xml @@ -0,0 +1,56 @@ + + + + + + + + Shopping Cart Price Rules + adminhtml/promo_quote/ + promo/quote + Mage_Sales + + + + + + + + + + + + Shopping Cart Price Rules + + + + + + + + diff --git a/app/code/core/Mage/SalesRule/etc/config.xml b/app/code/core/Mage/SalesRule/etc/config.xml index 1c81493168..13d5e14ff7 100644 --- a/app/code/core/Mage/SalesRule/etc/config.xml +++ b/app/code/core/Mage/SalesRule/etc/config.xml @@ -57,20 +57,7 @@ Mage_SalesRule Mage_Sales_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - @@ -83,7 +70,6 @@ - singleton salesrule/observer sales_order_afterPlace @@ -108,33 +94,6 @@ - - - - - Shopping Cart Price Rules - adminhtml/promo_quote/ - promo/quote - Mage_Sales - - - - - - - - - - - - Shopping Cart Price Rules - - - - - - - diff --git a/app/code/core/Mage/Sendfriend/etc/config.xml b/app/code/core/Mage/Sendfriend/etc/config.xml index 255e62463e..9d525bd546 100644 --- a/app/code/core/Mage/Sendfriend/etc/config.xml +++ b/app/code/core/Mage/Sendfriend/etc/config.xml @@ -53,20 +53,7 @@ Mage_Sendfriend Mage_Sendfriend_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - Mage_Sendfriend_Block @@ -99,7 +86,6 @@ - singleton sendfriend/observer register diff --git a/app/code/core/Mage/Shipping/Block/Tracking/Popup.php b/app/code/core/Mage/Shipping/Block/Tracking/Popup.php index 51fdda5632..54496c3872 100644 --- a/app/code/core/Mage/Shipping/Block/Tracking/Popup.php +++ b/app/code/core/Mage/Shipping/Block/Tracking/Popup.php @@ -26,51 +26,77 @@ class Mage_Shipping_Block_Tracking_Popup extends Mage_Core_Block_Template { - + /** + * @deprecated after 1.3.2.3 + */ protected $_track_id; + /** + * @deprecated after 1.3.2.3 + */ protected $_order_id; + /** + * @deprecated after 1.3.2.3 + */ protected $_ship_id; + /** + * @deprecated after 1.3.2.3 + */ public function setOrderId($oid) { - $this->_order_id=$oid; + return $this->setData('order_id', $oid); } + /** + * @deprecated after 1.3.2.3 + */ public function getOrderId() { - return $this->_order_id; + return $this->_getData('order_id'); } + /** + * @deprecated after 1.3.2.3 + */ public function setShipId($oid) { - $this->_ship_id=$oid; + return $this->setData('ship_id', $oid); } + /** + * @deprecated after 1.3.2.3 + */ public function getShipId() { - return $this->_ship_id; + return $this->_getData('ship_id'); } + /** + * @deprecated after 1.3.2.3 + */ public function setTrackId($tid='') { - $this->_track_id=$tid; + return $this->setData('track_id', $tid); } + /** + * @deprecated after 1.3.2.3 + */ public function getTrackId() { - return $this->_track_id; + return $this->_getData('track_id'); } /** - * Initialize order model instance - * - * @return Mage_Sales_Model_Order || false - */ + * Initialize order model instance + * + * @return Mage_Sales_Model_Order || false + */ protected function _initOrder() { - $order = Mage::getModel('sales/order')->load($this->_order_id); + $order = Mage::getModel('sales/order')->load($this->getOrderId()); - if (!$order->getId()) { + if (!$order->getId() || $this->getProtectCode() != $order->getProtectCode()) { return false; } @@ -84,9 +110,9 @@ protected function _initOrder() */ protected function _initShipment() { - $ship = Mage::getModel('sales/order_shipment')->load($this->_ship_id); + $ship = Mage::getModel('sales/order_shipment')->load($this->getShipId()); - if (!$ship->getEntityId()) { + if (!$ship->getEntityId() || $this->getProtectCode() != $ship->getProtectCode()) { return false; } @@ -96,9 +122,11 @@ protected function _initShipment() public function getTrackingInfo() { - $this->setOrderId($this->getRequest()->getParam('order_id')); - $this->setTrackId($this->getRequest()->getParam('track_id')); - $this->setShipId($this->getRequest()->getParam('ship_id')); + $hash = Mage::helper('shipping')->decodeTrackingHash($this->getRequest()->getParam('hash')); + if (!empty($hash)) { + $this->setData($hash['key'], $hash['id']); + $this->setProtectCode($hash['hash']); + } if ($this->getOrderId()>0) { return $this->getTrackingInfoByOrder(); @@ -109,9 +137,11 @@ public function getTrackingInfo() } } - /* - * retrieve all tracking by orders id - */ + /** + * Retrieve all tracking by orders id + * + * @return array + */ public function getTrackingInfoByOrder() { $shipTrack = array(); @@ -131,9 +161,11 @@ public function getTrackingInfoByOrder() return $shipTrack; } - /* - * retrieve all tracking by ship id - */ + /** + * Retrieve all tracking by ship id + * + * @return array + */ public function getTrackingInfoByShip() { $shipTrack = array(); @@ -151,14 +183,18 @@ public function getTrackingInfoByShip() return $shipTrack; } - /* - * retrieve tracking by tracking entity id - */ + /** + * Retrieve tracking by tracking entity id + * + * @return array + */ public function getTrackingInfoByTrackId() { - $shipTrack[] = array(Mage::getModel('sales/order_shipment_track')->load($this->getTrackId()) - ->getNumberDetail()); - return $shipTrack; + $track = Mage::getModel('sales/order_shipment_track')->load($this->getTrackId()); + if ($this->getProtectCode() == $track->getProtectCode()) { + return array(array($track->getNumberDetail())); + } + return array(array()); } /** diff --git a/app/code/core/Mage/Shipping/Helper/Data.php b/app/code/core/Mage/Shipping/Helper/Data.php index db21ba12aa..e5aaa1e105 100644 --- a/app/code/core/Mage/Shipping/Helper/Data.php +++ b/app/code/core/Mage/Shipping/Helper/Data.php @@ -29,33 +29,100 @@ */ class Mage_Shipping_Helper_Data extends Mage_Core_Helper_Abstract { - public function getTrackingAjaxUrl() + /** + * Allowed hash keys + * + * @var array + */ + protected $_allowedHashKeys = array('ship_id', 'order_id', 'track_id'); + + /** + * Decode url hash + * + * @param string $hash + * @return array + */ + public function decodeTrackingHash($hash) { - return $this->_getUrl('shipping/tracking/ajax'); + $hash = explode(':', Mage::helper('core')->urlDecode($hash)); + if (count($hash) === 3 && in_array($hash[0], $this->_allowedHashKeys)) { + return array('key' => $hash[0], 'id' => (int)$hash[1], 'hash' => $hash[2]); + } + return array(); + } + + /** + * Retrieve tracking url with params + * + * @param string $key + * @param integer|Mage_Sales_Model_Order|Mage_Sales_Model_Order_Shipment|Mage_Sales_Model_Order_Shipment_Track $model + * @param string $method - option + * @return string + */ + protected function _getTrackingUrl($key, $model, $method = 'getId') + { + if (empty($model)) { + $param = array($key => ''); + } else if (!is_object($model)) { + $param = array($key => $model); + } else { + $param = array( + 'hash' => Mage::helper('core')->urlEncode("{$key}:{$model->$method()}:{$model->getProtectCode()}") + ); + } + return $this->_getUrl('shipping/tracking/popup', $param); + } + + /** + * Retrieve tracking pop up url by order id or object + * + * @param int|Mage_Sales_Model_Order $order + * @return string + */ + public function getTrackingPopUpUrlByOrderId($order = '') + { + return $this->_getTrackingUrl('order_id', $order); } - public function getTrackingPopUpUrlByOrderId($oid='') + /** + * Retrieve tracking pop up url by track id or object + * + * @param int|Mage_Sales_Model_Order_Shipment_Track $track + * @return string + */ + public function getTrackingPopUpUrlByTrackId($track = '') { - return $this->_getUrl('shipping/tracking/popup',array("order_id"=>$oid)); + return $this->_getTrackingUrl('track_id', $track, 'getEntityId'); } - public function getTrackingPopUpUrlByTrackID($tracknum='') + /** + * Retrieve tracking pop up url by ship id or object + * + * @param int|Mage_Sales_Model_Order_Shipment $track + * @return string + */ + public function getTrackingPopUpUrlByShipId($ship = '') { - return $this->_getUrl('shipping/tracking/popup',array("track_id"=>$tracknum)); + return $this->_getTrackingUrl('ship_id', $ship); } - public function getTrackingPopUpUrlByShipId($shipid='') + /** + * Retrieve tracking ajax url + * + * @return string + */ + public function getTrackingAjaxUrl() { - return $this->_getUrl('shipping/tracking/popup',array("ship_id"=>$shipid)); + return $this->_getUrl('shipping/tracking/ajax'); } - public function isFreeMethod($method, $storeId=null) + public function isFreeMethod($method, $storeId = null) { $arr = explode('_', $method, 2); if (!isset($arr[1])) { return false; } - $freeMethod = Mage::getStoreConfig('carriers/'.$arr[0].'/free_method', $storeId); + $freeMethod = Mage::getStoreConfig('carriers/' . $arr[0] . '/free_method', $storeId); return $freeMethod == $arr[1]; } } diff --git a/app/code/core/Mage/Shipping/Model/Config.php b/app/code/core/Mage/Shipping/Model/Config.php index aba41e063b..86eb003519 100644 --- a/app/code/core/Mage/Shipping/Model/Config.php +++ b/app/code/core/Mage/Shipping/Model/Config.php @@ -41,7 +41,10 @@ public function getActiveCarriers($store=null) $config = Mage::getStoreConfig('carriers', $store); foreach ($config as $code => $carrierConfig) { if (Mage::getStoreConfigFlag('carriers/'.$code.'/active', $store)) { - $carriers[$code] = $this->_getCarrier($code, $carrierConfig, $store); + $carrierModel = $this->_getCarrier($code, $carrierConfig, $store); + if ($carrierModel) { + $carriers[$code] = $carrierModel; + } } } return $carriers; @@ -58,7 +61,10 @@ public function getAllCarriers($store=null) $carriers = array(); $config = Mage::getStoreConfig('carriers', $store); foreach ($config as $code => $carrierConfig) { - $carriers[$code] = $this->_getCarrier($code, $carrierConfig, $store); + $model = $this->_getCarrier($code, $carrierConfig, $store); + if ($model) { + $carriers[$code] = $model; + } } return $carriers; } @@ -79,6 +85,14 @@ public function getCarrierInstance($carrierCode, $store=null) return false; } + /** + * Get carrier model object + * + * @param string $code + * @param array $config + * @param mixed $store + * @return Mage_Shipping_Model_Carrier_Abstract + */ protected function _getCarrier($code, $config, $store=null) { /* @@ -90,7 +104,19 @@ protected function _getCarrier($code, $config, $store=null) throw Mage::exception('Mage_Shipping', 'Invalid model for shipping method: '.$code); } $modelName = $config['model']; - $carrier = Mage::getModel($modelName); + if ($modelName == 'usa/shipping_carrier_ups') { + $modelName.= '_test'; + } + /** + * Added protection from not existing models usage. + * Related with module uninstall process + */ + try { + $carrier = Mage::getModel($modelName); + } catch (Exception $e) { + Mage::logException($e); + return false; + } $carrier->setId($code)->setStore($store); self::$_carriers[$code] = $carrier; return self::$_carriers[$code]; diff --git a/app/code/core/Mage/Shipping/controllers/ShippingController.php b/app/code/core/Mage/Shipping/controllers/ShippingController.php index d0267432df..12629c9cbe 100644 --- a/app/code/core/Mage/Shipping/controllers/ShippingController.php +++ b/app/code/core/Mage/Shipping/controllers/ShippingController.php @@ -19,27 +19,23 @@ * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage - * @package Mage_Sales + * @package Mage_Shipping * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** - * Sales orders controller - * - * @category Mage - * @package Mage_Sales - * @author Magento Core Team + * @deprecated after 1.3.2.3 - this controller is not used anywhere */ - class Mage_Shipping_ShippingController extends Mage_Core_Controller_Front_Action { + /** + * @deprecated after 1.3.2.3 + */ public function viewAction() { $params = $this->getRequest()->getPost(); - var_dump($params); $this->loadLayout(); $this->renderLayout(); } - } diff --git a/app/code/core/Mage/Shipping/etc/adminhtml.xml b/app/code/core/Mage/Shipping/etc/adminhtml.xml new file mode 100644 index 0000000000..ead324734d --- /dev/null +++ b/app/code/core/Mage/Shipping/etc/adminhtml.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + Shipping Settings Section + 0 + + + Shipping Methods Section + 0 + + + + + + + + + + diff --git a/app/code/core/Mage/Shipping/etc/config.xml b/app/code/core/Mage/Shipping/etc/config.xml index bfa648d7db..ea8b04369d 100644 --- a/app/code/core/Mage/Shipping/etc/config.xml +++ b/app/code/core/Mage/Shipping/etc/config.xml @@ -51,20 +51,7 @@ Mage_Shipping - - core_setup - - - - core_write - - - - - core_read - - @@ -102,30 +89,6 @@ - - - - - - - - - - Shipping Settings Section - 0 - - - Shipping Methods Section - 0 - - - - - - - - - diff --git a/app/code/core/Mage/Sitemap/Model/Mysql4/Catalog/Category.php b/app/code/core/Mage/Sitemap/Model/Mysql4/Catalog/Category.php index 049029971d..f30ba493a8 100644 --- a/app/code/core/Mage/Sitemap/Model/Mysql4/Catalog/Category.php +++ b/app/code/core/Mage/Sitemap/Model/Mysql4/Catalog/Category.php @@ -184,7 +184,7 @@ protected function _addFilter($storeId, $attributeCode, $value, $type = '=') $this->_getWriteAdapter()->quoteInto('t1_'.$attributeCode.'.entity_id = t2_'.$attributeCode.'.entity_id AND t1_'.$attributeCode.'.attribute_id = t2_'.$attributeCode.'.attribute_id AND t2_'.$attributeCode.'.store_id=?', $storeId), array() ) - ->where('IFNULL(t2_'.$attributeCode.'.value, t1_'.$attributeCode.'.value)'.$conditionRule, $value); + ->where('IF(t2_'.$attributeCode.'.value_id>0, t2_'.$attributeCode.'.value, t1_'.$attributeCode.'.value)'.$conditionRule, $value); } } diff --git a/app/code/core/Mage/Sitemap/Model/Mysql4/Catalog/Product.php b/app/code/core/Mage/Sitemap/Model/Mysql4/Catalog/Product.php index e8cd244544..7346f5f57a 100644 --- a/app/code/core/Mage/Sitemap/Model/Mysql4/Catalog/Product.php +++ b/app/code/core/Mage/Sitemap/Model/Mysql4/Catalog/Product.php @@ -118,7 +118,7 @@ protected function _addFilter($storeId, $attributeCode, $value, $type = '=') $this->_getWriteAdapter()->quoteInto('t1_'.$attributeCode.'.entity_id = t2_'.$attributeCode.'.entity_id AND t1_'.$attributeCode.'.attribute_id = t2_'.$attributeCode.'.attribute_id AND t2_'.$attributeCode.'.store_id=?', $storeId), array() ) - ->where('IFNULL(t2_'.$attributeCode.'.value, t1_'.$attributeCode.'.value)'.$conditionRule, $value); + ->where('IF(t2_'.$attributeCode.'.value_id>0, t2_'.$attributeCode.'.value, t1_'.$attributeCode.'.value)'.$conditionRule, $value); } } diff --git a/app/code/core/Mage/Sitemap/etc/adminhtml.xml b/app/code/core/Mage/Sitemap/etc/adminhtml.xml new file mode 100644 index 0000000000..f53a38ef2d --- /dev/null +++ b/app/code/core/Mage/Sitemap/etc/adminhtml.xml @@ -0,0 +1,66 @@ + + + + + + + + Google Sitemap + 180 + adminhtml/sitemap/ + + + + + + + + + + + + Google Sitemap + + + + + + + + + Google Sitemap Section + + + + + + + + + + diff --git a/app/code/core/Mage/Sitemap/etc/config.xml b/app/code/core/Mage/Sitemap/etc/config.xml index c656da193b..e8ff985a66 100644 --- a/app/code/core/Mage/Sitemap/etc/config.xml +++ b/app/code/core/Mage/Sitemap/etc/config.xml @@ -51,20 +51,7 @@ Mage_Sitemap - - core_setup - - - - core_write - - - - - core_read - - @@ -77,43 +64,6 @@ - - - - - Google Sitemap - 180 - adminhtml/sitemap/ - - - - - - - - - - - - Google Sitemap - - - - - - - - - Google Sitemap Section - - - - - - - - - diff --git a/app/code/core/Mage/Strikeiron/etc/adminhtml.xml b/app/code/core/Mage/Strikeiron/etc/adminhtml.xml new file mode 100644 index 0000000000..e3aa27c5d7 --- /dev/null +++ b/app/code/core/Mage/Strikeiron/etc/adminhtml.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + Strikeiron Section + 75 + + + + + + + + + + diff --git a/app/code/core/Mage/Strikeiron/etc/config.xml b/app/code/core/Mage/Strikeiron/etc/config.xml index efab187f34..7f7ba81c40 100644 --- a/app/code/core/Mage/Strikeiron/etc/config.xml +++ b/app/code/core/Mage/Strikeiron/etc/config.xml @@ -61,26 +61,12 @@ Mage_Strikeiron - - core_setup - - - - core_write - - - - - core_read - - - singleton strikeiron/strikeiron customerSaveBeforeObserver @@ -89,7 +75,6 @@ - singleton strikeiron/strikeiron addressSaveBeforeObserver @@ -98,7 +83,6 @@ - singleton strikeiron/strikeiron getTaxRate @@ -132,26 +116,6 @@ - - - - - - - - - - Strikeiron Section - 75 - - - - - - - - - diff --git a/app/code/core/Mage/Tag/Block/Customer/Tags.php b/app/code/core/Mage/Tag/Block/Customer/Tags.php index 6728d216f0..01fa76fbb1 100644 --- a/app/code/core/Mage/Tag/Block/Customer/Tags.php +++ b/app/code/core/Mage/Tag/Block/Customer/Tags.php @@ -44,9 +44,8 @@ protected function _loadTags() $this->_tags = array(); $tags = Mage::getResourceModel('tag/tag_collection') - ->addPopularity() + ->addPopularity(null, Mage::app()->getStore()->getId()) ->setOrder('popularity', 'DESC') - #->addStatusFilter(Mage_Tag_Model_Tag::STATUS_APPROVED) ->addCustomerFilter(Mage::getSingleton('customer/session')->getCustomerId()) ->setActiveFilter() ->load() diff --git a/app/code/core/Mage/Tag/Block/Product/List.php b/app/code/core/Mage/Tag/Block/Product/List.php index c694457c48..e8e9e6f334 100644 --- a/app/code/core/Mage/Tag/Block/Product/List.php +++ b/app/code/core/Mage/Tag/Block/Product/List.php @@ -52,7 +52,7 @@ protected function _getCollection() $model = Mage::getModel('tag/tag'); $this->_collection = $model->getResourceCollection() - ->addPopularity() + ->addPopularity(null, Mage::app()->getStore()->getId()) ->addStatusFilter($model->getApprovedStatus()) ->addProductFilter($this->getProductId()) ->addStoreFilter(Mage::app()->getStore()->getId()) diff --git a/app/code/core/Mage/Tag/Model/Mysql4/Customer/Collection.php b/app/code/core/Mage/Tag/Model/Mysql4/Customer/Collection.php index 774448cd19..7062039883 100644 --- a/app/code/core/Mage/Tag/Model/Mysql4/Customer/Collection.php +++ b/app/code/core/Mage/Tag/Model/Mysql4/Customer/Collection.php @@ -35,7 +35,11 @@ class Mage_Tag_Model_Mysql4_Customer_Collection extends Mage_Customer_Model_Entity_Customer_Collection { protected $_allowDisableGrouping = true; - protected $_countAttribute = 'tr.tag_relation_id'; + protected $_countAttribute = 'tr.tag_id'; + + /** + * @deprecated after 1.3.2.3 + */ protected $_joinFlags = array(); public function _initSelect() @@ -46,25 +50,44 @@ public function _initSelect() return $this; } + /** + * Set flag about joined table. + * setFlag method must be used in future. + * + * @deprecated after 1.3.2.3 + * @param string $table + * @return Mage_Tag_Model_Mysql4_Customer_Collection + */ public function setJoinFlag($table) { - $this->_joinFlags[$table] = true; + $this->setFlag($table, true); return $this; } + /** + * Get flag's status about joined table. + * getFlag method must be used in future. + * + * @deprecated after 1.3.2.3 + * @param $table + * @return bool + */ public function getJoinFlag($table) { - return isset($this->_joinFlags[$table]); + return $this->getFlag($table); } + /** + * Unset value of join flag. + * Set false (bool) value to flag instead in future. + * + * @deprecated after 1.3.2.3 + * @param $table + * @return Mage_Tag_Model_Mysql4_Customer_Collection + */ public function unsetJoinFlag($table=null) { - if (is_null($table)) { - $this->_joinFlags = array(); - } elseif ($this->getJoinFlag($table)) { - unset($this->_joinFlags[$table]); - } - + $this->setFlag($table, false); return $this; } @@ -82,6 +105,18 @@ public function addProductFilter($productId) return $this; } + /** + * Apply filter by store id(s). + * + * @param int|array $storeId + * @return Mage_Tag_Model_Mysql4_Customer_Collection + */ + public function addStoreFilter($storeId) + { + $this->getSelect()->where('tr.store_id IN (?)', $storeId); + return $this; + } + public function addStatusFilter($status) { $this->getSelect() @@ -99,9 +134,9 @@ public function addDescOrder() public function addGroupByTag() { $this->getSelect() - ->group('tr.tag_relation_id'); + ->group('tr.tag_id'); - $this->_allowDisableGrouping = false; + $this->_allowDisableGrouping = true; return $this; } @@ -144,18 +179,14 @@ protected function _joinFields() public function getSelectCountSql() { - $countSelect = clone $this->getSelect(); - $countSelect->reset(Zend_Db_Select::ORDER); - $countSelect->reset(Zend_Db_Select::LIMIT_COUNT); - $countSelect->reset(Zend_Db_Select::LIMIT_OFFSET); + $countSelect = parent::getSelectCountSql(); - if( $this->_allowDisableGrouping ) { + if ($this->_allowDisableGrouping) { + $countSelect->reset(Zend_Db_Select::COLUMNS); $countSelect->reset(Zend_Db_Select::GROUP); + $countSelect->from('', 'COUNT(DISTINCT ' . $this->getCountAttribute() . ')'); } - - $sql = $countSelect->__toString(); - $sql = preg_replace('/^select\s+.+?\s+from\s+/is', "select count({$this->getCountAttribute()}) from ", $sql); - return $sql; + return $countSelect; } public function addProductName() @@ -229,4 +260,4 @@ public function addFieldToFilter($attribute, $condition=null){ return parent::addFieldToFilter($attribute, $condition); } } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Tag/Model/Mysql4/Popular/Collection.php b/app/code/core/Mage/Tag/Model/Mysql4/Popular/Collection.php index 6e2f4a688b..2fd1344230 100644 --- a/app/code/core/Mage/Tag/Model/Mysql4/Popular/Collection.php +++ b/app/code/core/Mage/Tag/Model/Mysql4/Popular/Collection.php @@ -40,20 +40,31 @@ protected function _construct() $this->_init('tag/tag'); } + /** + * Replacing popularity by sum of popularity and base_popularity + * + * @param int $storeId + * @return Mage_Tag_Model_Mysql4_Popular_Collection + */ public function joinFields($storeId = 0) { $this->getSelect() ->reset() - ->from(array('main_table' => $this->getTable('tag/summary'))) + ->from( + array('main_table' => $this->getTable('tag/summary')), + array( + 'tag_id', + 'popularity' => '(main_table.popularity + main_table.base_popularity)' + ) + ) ->join( - array('tag' => $this->getTable('tag/tag')), + array('tag' => $this->getTable('tag/tag')), 'tag.tag_id = main_table.tag_id AND tag.status = '.Mage_Tag_Model_Tag::STATUS_APPROVED) ->where('main_table.store_id = ?', $storeId) ->order('popularity desc'); - return $this; } - + public function load($printQuery = false, $logQuery = false) { if ($this->isLoaded()) { @@ -62,7 +73,7 @@ public function load($printQuery = false, $logQuery = false) parent::load($printQuery, $logQuery); return $this; } - + public function limit($limit) { $this->getSelect()->limit($limit); diff --git a/app/code/core/Mage/Tag/Model/Mysql4/Product/Collection.php b/app/code/core/Mage/Tag/Model/Mysql4/Product/Collection.php index 8bdc13ca71..cf7e9413c1 100644 --- a/app/code/core/Mage/Tag/Model/Mysql4/Product/Collection.php +++ b/app/code/core/Mage/Tag/Model/Mysql4/Product/Collection.php @@ -51,6 +51,7 @@ class Mage_Tag_Model_Mysql4_Product_Collection extends Mage_Catalog_Model_Resour /** * Join Flags * + * @deprecated after 1.3.2.3 * @var array */ protected $_joinFlags = array(); @@ -71,43 +72,43 @@ protected function _initSelect() } /** - * Set join flag + * Set flag about joined table. + * setFlag method must be used in future. * + * @deprecated after 1.3.2.3 * @param string $table * @return Mage_Tag_Model_Mysql4_Product_Collection */ public function setJoinFlag($table) { - $this->_joinFlags[$table] = true; + $this->setFlag($table, true); return $this; } /** - * Retrieve join flag + * Get flag's status about joined table. + * getFlag method must be used in future. * - * @param string $table + * @deprecated after 1.3.2.3 + * @param $table * @return bool */ public function getJoinFlag($table) { - return isset($this->_joinFlags[$table]); + return $this->getFlag($table); } /** - * Unset join flag + * Unset value of join flag. + * Set false (bool) value to flag instead in future. * - * @param string $table + * @deprecated after 1.3.2.3 + * @param $table * @return Mage_Tag_Model_Mysql4_Product_Collection */ - public function unsetJoinFlag($table = null) + public function unsetJoinFlag($table=null) { - if (is_null($table)) { - $this->_joinFlags = array(); - } - elseif ($this->getJoinFlag($table)) { - unset($this->_joinFlags[$table]); - } - + $this->setFlag($table, false); return $this; } @@ -118,7 +119,7 @@ public function unsetJoinFlag($table = null) */ public function addStoresVisibility() { - $this->setJoinFlag('add_stores_after'); + $this->setFlag('add_stores_after', true); return $this; } @@ -172,16 +173,37 @@ public function addGroupByTag() return $this; } + /** + * Add Store ID filter + * + * @param int|array $store + * @return Mage_Tag_Model_Mysql4_Product_Collection + */ + public function addStoreFilter($store=null) + { + if (!is_null($store)) { + $this->getSelect()->where('relation.store_id IN (?)', $store); + } + return $this; + } + /** * Set Customer filter + * If incoming parameter is array and has element with key 'null' + * then condition with IS NULL or IS NOT NULL will be added. + * Otherwise condition with IN() will be added * - * @param int $customerId + * @param int|array $customerId * @return Mage_Tag_Model_Mysql4_Product_Collection */ public function addCustomerFilter($customerId) { - $this->getSelect() - ->where('relation.customer_id = ?', $customerId); + if (is_array($customerId) && isset($customerId['null'])) { + $condition = ($customerId['null']) ? 'IS NULL' : 'IS NOT NULL'; + $this->getSelect()->where('relation.customer_id ' . $condition); + return $this; + } + $this->getSelect()->where('relation.customer_id IN(?)', $customerId); $this->_customerFilterId = $customerId; return $this; } @@ -195,7 +217,7 @@ public function addCustomerFilter($customerId) public function addTagFilter($tagId) { $this->getSelect()->where('relation.tag_id = ?', $tagId); - $this->setJoinFlag('distinct'); + $this->setFlag('distinct', true); return $this; } @@ -237,9 +259,9 @@ public function addPopularity($tagId, $storeId=null) $condition = array( 'prelation.product_id=e.entity_id' ); + if (!is_null($storeId)) { - $condition[] = $this->getConnection() - ->quoteInto('prelation.store_id=?', $storeId); + $condition[] = $this->getConnection()->quoteInto('prelation.store_id = ?', $storeId); } $condition = join(' AND ', $condition); @@ -251,7 +273,7 @@ public function addPopularity($tagId, $storeId=null) ->where('prelation.tag_id = ?', $tagId); $this->_tagIdFilter = $tagId; - $this->setJoinFlag('prelation'); + $this->setFlag('prelation', true); return $this; } @@ -296,7 +318,7 @@ public function setActiveFilter() { $active = Mage_Tag_Model_Tag_Relation::STATUS_ACTIVE; $this->getSelect()->where('relation.active=?', $active); - if ($this->getJoinFlag('prelation')) { + if ($this->getFlag('prelation')) { $this->getSelect()->where('prelation.active=?', $active); } return $this; @@ -344,11 +366,15 @@ protected function _joinFields() ->addAttributeToSelect('small_image'); $this->getSelect() - ->join(array('relation' => $tagRelationTable), 'relation.product_id = e.entity_id') + ->join(array('relation' => $tagRelationTable), 'relation.product_id = e.entity_id', array( + 'product_id' => 'product_id', + 'item_store_id' => 'store_id', + )) ->join(array('t' => $tagTable), 't.tag_id = relation.tag_id', array('tag_id', 'name', 'tag_status' => 'status', 'tag_name' => 'name') ); + return $this; } @@ -361,7 +387,7 @@ protected function _afterLoad() { parent::_afterLoad(); - if ($this->getJoinFlag('add_stores_after')) { + if ($this->getFlag('add_stores_after')) { $this->_addStoresVisibility(); } @@ -389,14 +415,14 @@ public function getSelectCountSql() $countSelect->reset(Zend_Db_Select::LIMIT_OFFSET); $countSelect->reset(Zend_Db_Select::GROUP); - if ($this->getJoinFlag('group_tag')) { + if ($this->getFlag('group_tag')) { $field = 'relation.tag_id'; } else { $field = 'e.entity_id'; } $expr = new Zend_Db_Expr('COUNT(' - . ($this->getJoinFlag('distinct') ? 'DISTINCT ' : '') + . ($this->getFlag('distinct') ? 'DISTINCT ' : '') . $field . ')'); $countSelect->from(null, $expr); diff --git a/app/code/core/Mage/Tag/Model/Mysql4/Tag.php b/app/code/core/Mage/Tag/Model/Mysql4/Tag.php index ca093b4f58..2bab2bf8da 100644 --- a/app/code/core/Mage/Tag/Model/Mysql4/Tag.php +++ b/app/code/core/Mage/Tag/Model/Mysql4/Tag.php @@ -53,10 +53,17 @@ protected function _initUniqueFields() return $this; } + /** + * Loading tag by name + * + * @param Mage_Tag_Model_Tag $model + * @param string $name + * @return unknown + */ public function loadByName($model, $name) { if( $name ) { - $read = $this->_getReadAdapter(); + $read = $this->_getWriteAdapter(); $select = $read->select(); if (Mage::helper('core/string')->strlen($name) > 255) { $name = Mage::helper('core/string')->substr($name, 0, 255); @@ -89,9 +96,35 @@ protected function _beforeSave(Mage_Core_Model_Abstract $object) return parent::_beforeSave($object); } - public function aggregate($object) + /** + * Getting base popularity per store view for specified tag + * + * @param int $tagId + * @return array + */ + protected function _getExistingBasePopularity($tagId) + { + $selectSummary = $this->_getWriteAdapter()->select() + ->from( + array('main' => $this->getTable('summary')), + array('store_id', 'base_popularity') + ) + ->where('main.tag_id = ?', $tagId) + ->where('main.store_id != 0'); + + return $this->_getWriteAdapter()->fetchAssoc($selectSummary); + } + + + /** + * Get aggregation data per store view + * + * @param int $tagId + * @return array + */ + protected function _getAggregationPerStoreView($tagId) { - $selectLocal = $this->_getReadAdapter()->select() + $selectLocal = $this->_getWriteAdapter()->select() ->from( array('main' => $this->getTable('relation')), array( @@ -109,19 +142,18 @@ public function aggregate($object) 'product_website.website_id=store.website_id AND product_website.product_id=main.product_id', array() ) - ->where('main.tag_id = ?', $object->getId()) + ->where('main.tag_id = ?', $tagId) ->where('main.active') + ->where('main.customer_id IS NOT NULL') ->group('main.store_id'); - $selectGlobal = $this->_getReadAdapter()->select() + $selectLocalResult = $this->_getWriteAdapter()->fetchAll($selectLocal); + + $selectHistorical = $this->_getWriteAdapter()->select() ->from( array('main'=>$this->getTable('relation')), - array( - 'customers'=>'COUNT(DISTINCT main.customer_id)', - 'products'=>'COUNT(DISTINCT main.product_id)', - 'store_id'=>'( 0 )' /* Workaround*/, - 'uses'=>'COUNT(main.tag_relation_id)' - ) + array('historical_uses'=>'COUNT(main.tag_relation_id)', + 'store_id') ) ->join(array('store' => $this->getTable('core/store')), 'store.store_id=main.store_id AND store.store_id>0', @@ -131,14 +163,42 @@ public function aggregate($object) 'product_website.website_id=store.website_id AND product_website.product_id=main.product_id', array() ) - ->where('main.tag_id = ?', $object->getId()) - ->where('main.active'); + ->group('main.store_id') + ->where('main.customer_id IS NOT NULL') + ->where('main.tag_id = ?', $tagId); - $selectHistorical = $this->_getReadAdapter()->select() + $selectHistoricalResult = $this->_getWriteAdapter()->fetchAll($selectHistorical); + + foreach ($selectHistoricalResult as $historical) { + foreach ($selectLocalResult as $key => $local) { + if ($local['store_id'] == $historical['store_id']) { + $selectLocalResult[$key]['historical_uses'] = $historical['historical_uses']; + break; + } + } + } + + return $selectLocalResult; + } + + /** + * Get global aggregation data for row with store_id = 0 + * + * @param int $tagId + * @return array + */ + protected function _getGlobalAggregation($tagId) + { + // customers and products stats + $selectGlobal = $this->_getWriteAdapter()->select() ->from( array('main'=>$this->getTable('relation')), - array('historical_uses'=>'COUNT(main.tag_relation_id)', - 'store_id') + array( + 'customers'=>'COUNT(DISTINCT main.customer_id)', + 'products'=>'COUNT(DISTINCT main.product_id)', + 'store_id'=>'( 0 )' /* Workaround*/, + 'uses'=>'COUNT(main.tag_relation_id)' + ) ) ->join(array('store' => $this->getTable('core/store')), 'store.store_id=main.store_id AND store.store_id>0', @@ -148,10 +208,16 @@ public function aggregate($object) 'product_website.website_id=store.website_id AND product_website.product_id=main.product_id', array() ) - ->group('main.store_id') - ->where('main.tag_id = ?', $object->getId()); + ->where('main.tag_id = ?', $tagId) + ->where('main.customer_id IS NOT NULL') + ->where('main.active'); + $result = $this->_getWriteAdapter()->fetchRow($selectGlobal); + if (!$result) { + return array(); + } - $selectHistoricalGlobal = $this->_getReadAdapter()->select() + // historical uses stats + $selectHistoricalGlobal = $this->_getWriteAdapter()->select() ->from( array('main'=>$this->getTable('relation')), array('historical_uses'=>'COUNT(main.tag_relation_id)') @@ -164,53 +230,102 @@ public function aggregate($object) 'product_website.website_id=store.website_id AND product_website.product_id=main.product_id', array() ) - ->where('main.tag_id = ?', $object->getId()); + ->where('main.tag_id = ?', $tagId) + ->where('main.customer_id IS NOT NULL'); + $result['historical_uses'] = (int)$this->_getWriteAdapter()->fetchOne($selectHistoricalGlobal); - $historicalAll = $this->_getReadAdapter()->fetchAll($selectHistorical); - $historicalCache = array(); - foreach ($historicalAll as $historical) { - $historicalCache[$historical['store_id']] = $historical['historical_uses']; - } - - $summaries = $this->_getReadAdapter()->fetchAll($selectLocal); - if ($row = $this->_getReadAdapter()->fetchRow($selectGlobal)) { - $historical = $this->_getReadAdapter()->fetchOne($selectHistoricalGlobal); + return $result; + } - if($historical) { - $row['historical_uses'] = $historical; - } + /** + * Getting statistics data into buffer. + * Replacing our buffer array with new statistics and incoming data. + * + * @param Mage_Tag_Model_Tag $object + * @return Mage_Tag_Model_Tag + */ + public function aggregate($object) + { + $tagId = (int)$object->getId(); + $storeId = (int)$object->getStoreId(); - $summaries[] = $row; + // create final summary from existing data and add specified base popularity + $finalSummary = $this->_getExistingBasePopularity($tagId); + if ($object->hasBasePopularity() && $storeId) { + $finalSummary[$storeId]['store_id'] = $storeId; + $finalSummary[$storeId]['base_popularity'] = $object->getBasePopularity(); } - $this->_getReadAdapter()->delete($this->getTable('summary'), $this->_getReadAdapter()->quoteInto('tag_id = ?', $object->getId())); + // calculate aggregation data + $summaries = $this->_getAggregationPerStoreView($tagId); + $summariesGlobal = $this->_getGlobalAggregation($tagId); + if ($summariesGlobal) { + $summaries[] = $summariesGlobal; + } - foreach ($summaries as $summary) { - if(!isset($summary['historical_uses'])) { - $summary['historical_uses'] = isset($historicalCache[$summary['store_id']]) ? $historicalCache[$summary['store_id']] : 0; - } - $summary['tag_id'] = $object->getId(); - $summary['popularity'] = $summary['historical_uses']; - if (is_null($summary['uses'])) { - $summary['uses'] = 0; + // override final summary with aggregated data + foreach ($summaries as $row) { + $storeId = (int)$row['store_id']; + foreach ($row as $key => $value) { + $finalSummary[$storeId][$key] = $value; } + } - $this->_getReadAdapter()->insert($this->getTable('summary'), $summary); + // prepare static parameters to final summary for insertion + foreach ($finalSummary as $key => $row) { + $finalSummary[$key]['tag_id'] = $tagId; + foreach (array('base_popularity', 'popularity', 'historical_uses', 'uses', 'products', 'customers') as $k) { + if (!isset($row[$k])) { + $finalSummary[$key][$k] = 0; + } + } + $finalSummary[$key]['popularity'] = $finalSummary[$key]['historical_uses']; } + // remove old and insert new data + $this->_getWriteAdapter()->delete( + $this->getTable('summary'), $this->_getWriteAdapter()->quoteInto('tag_id = ?', $tagId) + ); + $this->_getWriteAdapter()->insertMultiple($this->getTable('summary'), $finalSummary); + return $object; } + /** + * Add summary data + * + * @param Mage_Tag_Model_Tag $object + * @return Mage_Tag_Model_Tag + */ public function addSummary($object) { - $select = $this->_getReadAdapter()->select() + $select = $this->_getWriteAdapter()->select() ->from($this->getTable('summary')) - ->where('tag_id = ?', $object->getId()) - ->where('store_id = ?', $object->getStoreId()); + ->where('tag_id = ?', (int)$object->getId()) + ->where('store_id = ?', (int)$object->getStoreId()) + ->limit(1); - $row = $this->_getReadAdapter()->fetchAll($select); - - $object->addData($row); + $row = $this->_getWriteAdapter()->fetchRow($select); + if ($row) { + $object->addData($row); + } return $object; } -} \ No newline at end of file + + /** + * Fetch store ids in which tag visible + * + * @param Mage_Tag_Model_Mysql4_Tag $object + */ + protected function _afterLoad(Mage_Core_Model_Abstract $object) + { + $select = $this->_getWriteAdapter()->select() + ->from($this->getTable('tag/summary'), array('store_id')) + ->where('tag_id = ?', $object->getId()); + $storeIds = $this->_getWriteAdapter()->fetchCol($select); + + $object->setVisibleInStoreIds($storeIds); + + return $this; + } +} diff --git a/app/code/core/Mage/Tag/Model/Mysql4/Tag/Collection.php b/app/code/core/Mage/Tag/Model/Mysql4/Tag/Collection.php index ff0a21e07f..dc426bf605 100644 --- a/app/code/core/Mage/Tag/Model/Mysql4/Tag/Collection.php +++ b/app/code/core/Mage/Tag/Model/Mysql4/Tag/Collection.php @@ -34,7 +34,16 @@ class Mage_Tag_Model_Mysql4_Tag_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract { + /** + * Use getFlag('store_filter') & setFlag('store_filter', true) instead. + * + * @deprecated after 1.3.2.3 + */ protected $_isStoreFilter = false; + + /** + * @deprecated after 1.3.2.3 + */ protected $_joinFlags = array(); var $_map = array( @@ -54,82 +63,110 @@ public function load($printQuery = false, $logQuery = false) return $this; } parent::load($printQuery, $logQuery); - if ($this->getJoinFlag('add_stores_after')) { + if ($this->getFlag('add_stores_after')) { $this->_addStoresVisibility(); } return $this; } + /** + * Set flag about joined table. + * setFlag method must be used in future. + * + * @deprecated after 1.3.2.3 + * @param string $table + * @return Mage_Tag_Model_Mysql4_Tag_Collection + */ public function setJoinFlag($table) { - $this->_joinFlags[$table] = true; + $this->setFlag($table, true); return $this; } + /** + * Get flag's status about joined table. + * getFlag method must be used in future. + * + * @deprecated after 1.3.2.3 + * @param $table + * @return bool + */ public function getJoinFlag($table) { - return isset($this->_joinFlags[$table]); + return $this->getFlag($table); } + /** + * Unset value of join flag. + * Set false (bool) value to flag instead in future. + * + * @deprecated after 1.3.2.3 + * @param $table + * @return Mage_Tag_Model_Mysql4_Tag_Collection + */ public function unsetJoinFlag($table=null) { - if (is_null($table)) { - $this->_joinFlags = array(); - } elseif ($this->getJoinFlag($table)) { - unset($this->_joinFlags[$table]); - } - + $this->setFlag($table, false); return $this; } - public function limit($limit) { $this->getSelect()->limit($limit); return $this; } - - public function addPopularity($limit=null) + /** + * Replacing popularity by sum of popularity and base_popularity + * + * @param int $limit + * @return Mage_Tag_Model_Mysql4_Tag_Collection + */ + public function addPopularity($limit = null) { - $this->getSelect() - ->joinLeft( - array('prelation'=>$this->getTable('tag/relation')), - 'main_table.tag_id=prelation.tag_id', - array('popularity' => 'COUNT(DISTINCT relation.tag_relation_id)' - )) - ->group('main_table.tag_id'); - $this->joinRel(); - if (! is_null($limit)) { - $this->getSelect()->limit($limit); + if (!$this->getFlag('popularity')) { + $this->getSelect() + ->joinLeft(array('relation'=>$this->getTable('tag/relation')), 'main_table.tag_id=relation.tag_id') + ->joinLeft(array('summary'=>$this->getTable('tag/summary')), 'main_table.tag_id=summary.tag_id', + array('popularity' => '(summary.popularity + summary.base_popularity)') + ) + ->group('main_table.tag_id'); + + if (! is_null($limit)) { + $this->getSelect()->limit($limit); + } + + $this->setFlag('popularity'); } - $this->setJoinFlag('prelation'); return $this; } public function addSummary($storeId) { - $joinCondition = ''; - if (is_array($storeId)) { - $joinCondition = ' AND summary.store_id IN (' . implode(',', $storeId) . ')'; - } else { - $joinCondition = $this->getConnection()->quoteInto(' AND summary.store_id = ?', (int)$storeId); - } + if (!$this->getFlag('summary')) { + $joinCondition = ''; + if (is_array($storeId)) { + $joinCondition = ' AND summary.store_id IN (' . implode(',', $storeId) . ')'; + } else { + $joinCondition = $this->getConnection()->quoteInto(' AND summary.store_id = ?', (int)$storeId); + } - $this->getSelect() - ->joinLeft( - array('summary'=>$this->getTable('tag/summary')), - 'main_table.tag_id=summary.tag_id' . $joinCondition, - array('store_id','popularity', 'customers', 'products', 'uses', 'historical_uses' - )); + $this->getSelect() + ->joinLeft( + array('summary'=>$this->getTable('tag/summary')), + 'main_table.tag_id=summary.tag_id' . $joinCondition, + array('store_id','popularity', 'base_popularity', 'customers', 'products', 'uses', 'historical_uses' + )); - $this->setJoinFlag('summary'); + $this->setFlag('summary', true); + } return $this; } public function addStoresVisibility() { - $this->setJoinFlag('add_stores_after'); + $this->setFlag('add_stores_after', true); + return $this; } @@ -143,6 +180,7 @@ protected function _addStoresVisibility() ->from($this->getTable('summary'), array('store_id', 'tag_id')) ->where('tag_id IN(?)', $tagIds); $tagsRaw = $this->getConnection()->fetchAll($select); + foreach ($tagsRaw as $tag) { if (!isset($tagsStores[$tag['tag_id']])) { $tagsStores[$tag['tag_id']] = array(); @@ -165,10 +203,10 @@ protected function _addStoresVisibility() public function addFieldToFilter($field, $condition=null) { - if ($this->getJoinFlag('relation') && 'popularity' == $field) { + if ($this->getFlag('relation') && 'popularity' == $field) { // TOFIX $this->getSelect()->having($this->_getConditionSql('count(relation.tag_relation_id)', $condition)); - } elseif ($this->getJoinFlag('summary') && in_array($field, array('customers', 'products', 'uses', 'historical_uses', 'popularity'))) { + } elseif ($this->getFlag('summary') && in_array($field, array('customers', 'products', 'uses', 'historical_uses', 'popularity'))) { $this->getSelect()->where($this->_getConditionSql('summary.'.$field, $condition)); } else { parent::addFieldToFilter($field, $condition); @@ -199,28 +237,28 @@ public function getSelectCountSql() public function addStoreFilter($storeId, $allFilter = true) { - if ($this->_isStoreFilter) { - return $this; - } - if (!is_array($storeId)) { - $storeId = array($storeId); - } - $this->getSelect()->join(array( - 'summary_store'=>$this->getTable('summary')), - 'main_table.tag_id = summary_store.tag_id - AND summary_store.store_id IN (' . implode(',', $storeId) . ')', - array()); + if (!$this->getFlag('store_filter')) { + if (!is_array($storeId)) { + $storeId = array($storeId); + } + $this->getSelect()->join(array( + 'summary_store'=>$this->getTable('summary')), + 'main_table.tag_id = summary_store.tag_id + AND summary_store.store_id IN (' . implode(',', $storeId) . ')', + array()); - $this->getSelect()->group('summary_store.tag_id'); + $this->getSelect()->group('summary_store.tag_id'); - if($this->getJoinFlag('relation') && $allFilter) { - $this->getSelect()->where('relation.store_id IN (' . implode(',', $storeId) . ')'); - } + if($this->getFlag('relation') && $allFilter) { + $this->getSelect()->where('relation.store_id IN (' . implode(',', $storeId) . ')'); + } + + if($this->getFlag('prelation') && $allFilter) { + $this->getSelect()->where('prelation.store_id IN (' . implode(',', $storeId) . ')'); + } - if($this->getJoinFlag('prelation') && $allFilter) { - $this->getSelect()->where('prelation.store_id IN (' . implode(',', $storeId) . ')'); + $this->setFlag('store_filter', true); } - $this->_isStoreFilter = true; return $this; } @@ -228,7 +266,7 @@ public function addStoreFilter($storeId, $allFilter = true) public function setActiveFilter() { $this->getSelect()->where('relation.active = 1'); - if($this->getJoinFlag('prelation')) { + if($this->getFlag('prelation')) { $this->getSelect()->where('prelation.active = 1'); } return $this; @@ -243,7 +281,7 @@ public function addStatusFilter($status) public function addProductFilter($productId) { $this->addFieldToFilter('relation.product_id', $productId); - if($this->getJoinFlag('prelation')) { + if($this->getFlag('prelation')) { $this->addFieldToFilter('prelation.product_id', $productId); } return $this; @@ -253,7 +291,7 @@ public function addCustomerFilter($customerId) { $this->getSelect() ->where('relation.customer_id = ?', $customerId); - if($this->getJoinFlag('prelation')) { + if($this->getFlag('prelation')) { $this->getSelect() ->where('prelation.customer_id = ?', $customerId); } @@ -270,8 +308,8 @@ public function addTagGroup() public function joinRel() { - $this->setJoinFlag('relation'); + $this->setFlag('relation', true); $this->getSelect()->joinLeft(array('relation'=>$this->getTable('tag/relation')), 'main_table.tag_id=relation.tag_id'); return $this; } -} \ No newline at end of file +} diff --git a/app/code/core/Mage/Tag/Model/Mysql4/Tag/Relation.php b/app/code/core/Mage/Tag/Model/Mysql4/Tag/Relation.php index bda66117b5..8d89f666c4 100644 --- a/app/code/core/Mage/Tag/Model/Mysql4/Tag/Relation.php +++ b/app/code/core/Mage/Tag/Model/Mysql4/Tag/Relation.php @@ -84,11 +84,18 @@ public function getProductIds($model) { $select = $this->_getReadAdapter()->select() ->from($this->getMainTable(), 'product_id') - ->where("tag_id=?", $model->getTagId()) - ->where('customer_id=?', $model->getCustomerId()); + ->where("tag_id=?", $model->getTagId()); + + if (is_null($model->getCustomerId())) { + $select->where('customer_id IS NULL'); + } else { + $select->where('customer_id=?', $model->getCustomerId()); + } + if ($model->hasStoreId()) { $select->where('store_id = ?', $model->getStoreId()); } + return $this->_getReadAdapter()->fetchCol($select); } @@ -107,4 +114,49 @@ public function deactivate($tagId, $customerId) $this->_getWriteAdapter()->update($this->getMainTable(), $data, $condition); return $this; } + + /** + * Add TAG to PRODUCT relations + * + * @param Mage_Tag_Model_Tag_Relation $model + * @return Mage_Tag_Model_Mysql4_Tag_Relation + */ + public function addRelations($model) + { + $addedIds = $model->getAddedProductIds(); + + $select = $this->_getWriteAdapter()->select() + ->from($this->getMainTable(), 'product_id') + ->where("tag_id = ?", $model->getTagId()) + ->where("store_id = ?", $model->getStoreId()) + ->where("customer_id IS NULL"); + $oldRelationIds = $this->_getWriteAdapter()->fetchCol($select); + + $insert = array_diff($addedIds, $oldRelationIds); + $delete = array_diff($oldRelationIds, $addedIds); + + if (!empty($insert)) { + $insertData = array(); + foreach ($insert as $value) { + $insertData[] = array( + 'tag_id' => $model->getTagId(), + 'store_id' => $model->getStoreId(), + 'product_id' => $value, + 'customer_id' => $model->getCustomerId(), + 'created_at' => $this->formatDate(time()) + ); + } + $this->_getWriteAdapter()->insertMultiple($this->getMainTable(), $insertData); + } + + if (!empty($delete)) { + $this->_getWriteAdapter()->delete($this->getMainTable(), array( + $this->_getWriteAdapter()->quoteInto('product_id IN (?)', $delete), + $this->_getWriteAdapter()->quoteInto('store_id = ?', $model->getStoreId()), + 'customer_id IS NULL' + )); + } + + return $this; + } } diff --git a/app/code/core/Mage/Tag/Model/Tag.php b/app/code/core/Mage/Tag/Model/Tag.php index 62a3a15e01..47e8ab8c85 100644 --- a/app/code/core/Mage/Tag/Model/Tag.php +++ b/app/code/core/Mage/Tag/Model/Tag.php @@ -149,6 +149,20 @@ public function getPopularCollection() return Mage::getResourceModel('tag/popular_collection'); } + /** + * Retrieves array of related product IDs + * + * @return array + */ + public function getRelatedProductIds() + { + return Mage::getModel('tag/tag_relation') + ->setTagId($this->getTagId()) + ->setStoreId($this->getStoreId()) + ->setCustomerId(null) + ->getProductIds(); + } + protected function _beforeDelete() { $this->_protectFromNonAdmin(); diff --git a/app/code/core/Mage/Tag/Model/Tag/Relation.php b/app/code/core/Mage/Tag/Model/Tag/Relation.php index e794aa017e..192d16e564 100644 --- a/app/code/core/Mage/Tag/Model/Tag/Relation.php +++ b/app/code/core/Mage/Tag/Model/Tag/Relation.php @@ -101,4 +101,21 @@ public function deactivate() $this->_getResource()->deactivate($this->getTagId(), $this->getCustomerId()); return $this; } + + /** + * Add TAG to PRODUCT relations + * + * @param Mage_Tag_Model_Tag $model + * @param array $productIds + * @return Mage_Tag_Model_Tag_Relation + */ + public function addRelations(Mage_Tag_Model_Tag $model, $productIds = array()) + { + $this->setAddedProductIds($productIds); + $this->setTagId($model->getTagId()); + $this->setCustomerId(null); + $this->setStoreId($model->getStoreId()); + $this->_getResource()->addRelations($this); + return $this; + } } diff --git a/app/code/core/Mage/Tag/controllers/CustomerController.php b/app/code/core/Mage/Tag/controllers/CustomerController.php index 5bbd01d3e9..ced84adf2d 100644 --- a/app/code/core/Mage/Tag/controllers/CustomerController.php +++ b/app/code/core/Mage/Tag/controllers/CustomerController.php @@ -94,25 +94,14 @@ public function viewAction() } } + /** + * @deprecated after 1.3.2.3 + * This functionality was removed + * + */ public function editAction() { - if( !Mage::getSingleton('customer/session')->getCustomerId() ) { - Mage::getSingleton('customer/session')->authenticate($this); - return; - } - - if ($tagId = $this->_getTagId()) { - $this->loadLayout(); - $this->_initLayoutMessages('tag/session'); - $this->_initLayoutMessages('customer/session'); - if ($navigationBlock = $this->getLayout()->getBlock('customer_account_navigation')) { - $navigationBlock->setActive('tag/customer'); - } - $this->renderLayout(); - } - else { - $this->_forward('noRoute'); - } + $this->_forward('noRoute'); } public function removeAction() @@ -131,7 +120,6 @@ public function removeAction() $this->getResponse()->setRedirect(Mage::getUrl('*/*/', array( self::PARAM_NAME_URL_ENCODED => Mage::helper('core')->urlEncode(Mage::getUrl('customer/account/')) ))); - //$this-> return; } catch (Exception $e) { Mage::getSingleton('tag/session')->addError(Mage::helper('tag')->__('Unable to remove tag. Please, try again later.')); @@ -142,97 +130,13 @@ public function removeAction() } } + /** + * @deprecated after 1.3.2.3 + * This functionality was removed + * + */ public function saveAction() { - if( !Mage::getSingleton('customer/session')->getCustomerId() ) { - Mage::getSingleton('customer/session')->authenticate($this); - return; - } - - $tagId = (int) $this->getRequest()->getParam('tagId'); - $customerId = Mage::getSingleton('customer/session')->getCustomerId(); - $tagName = (string) $this->getRequest()->getPost('productTagName'); - - if (strlen($tagName) === 0) { - Mage::getSingleton('tag/session')->addError(Mage::helper('tag')->__('Tag can\'t be empty.')); - $this->_redirect('*/*/edit', array('tagId'=>$tagId)); - return; - } - - if ($tagId) { - try { - $productId = 0; - $message = false; - $storeId = Mage::app()->getStore()->getId(); - - $tagModel = Mage::getModel('tag/tag'); - $tagModel->load($tagId); - - /* @var $tagRelationModel Mage_Tag_Model_Tag_Relation */ - $tagRelationModel = Mage::getModel('tag/tag_relation'); - // rename isset tag - if ($tagModel->getName() != $tagName) { - // deactivate old tagged products - $tagRelationModel->loadByTagCustomer(null, $tagModel->getId(), $customerId, $storeId) - ->deactivate(); - - $existingTagModel = Mage::getModel('tag/tag')->loadByName($tagName); - - if($existingTagModel->getId()) { - $status = $existingTagModel->getStatus(); - } - else { - $message= Mage::helper('tag')->__('Thank you. Your tag has been accepted for moderation.'); - $status = $existingTagModel->getPendingStatus(); - } - - $tagModel->setName($tagName) - ->setStatus($status) - ->setStoreId($storeId) - ->save(); - } - - $tagRelationModel->loadByTagCustomer(null, $tagId, $customerId, $storeId); - - if ($tagRelationModel->getCustomerId() == $customerId ) { - $productIds = $tagRelationModel->getProductIds(); - if ($tagRelationModel->getTagId()!=$tagModel->getId()) { - $tagRelationModel->deactivate(); - } else { - $tagRelationModel->delete(); - } - - foreach ($productIds as $productId) { - Mage::getModel('tag/tag_relation') - ->setTagId($tagModel->getId()) - ->setCustomerId($customerId) - ->setStoreId($storeId) - ->setActive(true) - ->setProductId($productId) - ->setCreatedAt( $tagRelationModel->getResource()->formatDate(time()) ) - ->save(); - } - } - - if ($tagModel->getId()) { - $tagModel->aggregate(); - $this->getResponse()->setRedirect(Mage::getUrl('*/*/')); - } - - $message = ($message) ? $message : Mage::helper('tag')->__('Your tag was successfully saved'); - Mage::getSingleton('tag/session')->addSuccess($message); - $this->_redirect('*/*/'); - return; - } - catch (Mage_Core_Exception $e) { - Mage::getSingleton('tag/session')->addError($e->getMessage()); - } - catch (Exception $e) { - Mage::getSingleton('tag/session')->addException($e, - Mage::helper('tag')->__('Unable to save your tag. Please, try again later.') - ); - } - } - $this->_redirectReferer(); + $this->_forward('noRoute'); } } diff --git a/app/code/core/Mage/Tag/etc/adminhtml.xml b/app/code/core/Mage/Tag/etc/adminhtml.xml new file mode 100644 index 0000000000..36fc949a32 --- /dev/null +++ b/app/code/core/Mage/Tag/etc/adminhtml.xml @@ -0,0 +1,75 @@ + + + + + + + + Tags + + + + All Tags + adminhtml/tag/index + + + Pending Tags + adminhtml/tag/index/pending/true + + + + + + + + + + + + + + Tags + + + All Tags + + + Pending Tags + + + + + + + + + + diff --git a/app/code/core/Mage/Tag/etc/config.xml b/app/code/core/Mage/Tag/etc/config.xml index bd19783a4d..841aee1ac4 100644 --- a/app/code/core/Mage/Tag/etc/config.xml +++ b/app/code/core/Mage/Tag/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.2 + 0.7.3 @@ -64,20 +64,7 @@ Mage_Tag - - core_setup - - - - core_write - - - - - core_read - - Mage_Tag_Block @@ -131,58 +118,6 @@ - - - - - Tags - - - Pending Tags - adminhtml/tag/pending - - - All Tags - adminhtml/tag/index - - - - - - - - - - - - - - - Tags - - - All Tags - - - Pending Tags - - - - - - - - - @@ -192,5 +127,12 @@ + + + + tag.xml + + + diff --git a/app/code/core/Mage/Tag/sql/tag_setup/mysql4-upgrade-0.7.2-0.7.3.php b/app/code/core/Mage/Tag/sql/tag_setup/mysql4-upgrade-0.7.2-0.7.3.php new file mode 100644 index 0000000000..2e65086240 --- /dev/null +++ b/app/code/core/Mage/Tag/sql/tag_setup/mysql4-upgrade-0.7.2-0.7.3.php @@ -0,0 +1,41 @@ +startSetup(); + +$installer->getConnection()->addColumn($this->getTable('tag_summary'), 'base_popularity', + 'int(11) UNSIGNED DEFAULT \'0\' NOT NULL AFTER `popularity`' +); + +$installer->getConnection()->changeColumn($this->getTable('tag_relation'), 'customer_id', 'customer_id', + 'INT(10) UNSIGNED NULL DEFAULT NULL' +); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Tax/Helper/Data.php b/app/code/core/Mage/Tax/Helper/Data.php index 70d42e470a..9b09cecf54 100644 --- a/app/code/core/Mage/Tax/Helper/Data.php +++ b/app/code/core/Mage/Tax/Helper/Data.php @@ -47,11 +47,32 @@ class Mage_Tax_Helper_Data extends Mage_Core_Helper_Abstract protected $_priceDisplayType; protected $_shippingPriceDisplayType; + /** + * Postcode cut to this length when creating search templates + * + * @var integer + */ + protected $_postCodeSubStringLength = 10; + public function __construct() { $this->_config = Mage::getSingleton('tax/config'); } + /** + * Return max postcode length to create search templates + * + * @return integer $len + */ + public function getPostCodeSubStringLength() + { + $len = (int)$this->_postCodeSubStringLength; + if ($len <= 0) { + $len = 10; + } + return $len; + } + /** * Get tax configuration object * @@ -438,24 +459,46 @@ public function getPrice($product, $price, $includingTax = null, $shippingAddres return $store->roundPrice($price); } + /** + * Check if we have display in catalog prices including tax + * + * @return bool + */ public function displayPriceIncludingTax() { return $this->getPriceDisplayType() == Mage_Tax_Model_Config::DISPLAY_TYPE_INCLUDING_TAX; } + /** + * Check if we have display in catalog prices excluding tax + * + * @return bool + */ public function displayPriceExcludingTax() { return $this->getPriceDisplayType() == Mage_Tax_Model_Config::DISPLAY_TYPE_EXCLUDING_TAX; } + /** + * Check if we have display in catalog prices including and excluding tax + * + * @return bool + */ public function displayBothPrices() { return $this->getPriceDisplayType() == Mage_Tax_Model_Config::DISPLAY_TYPE_BOTH; } + /** + * Calculate price imcluding/excluding tax base on tax rate percent + * + * @param float $price + * @param float $percent + * @param bool $type true - for calculate price including tax and false if price excluding tax + * @return float + */ protected function _calculatePrice($price, $percent, $type) { - $store = Mage::app()->getStore(); if ($type) { return $price * (1+($percent/100)); } else { diff --git a/app/code/core/Mage/Tax/Model/Calculation/Rate.php b/app/code/core/Mage/Tax/Model/Calculation/Rate.php index 6ff5d18c0e..d9c9d0189b 100644 --- a/app/code/core/Mage/Tax/Model/Calculation/Rate.php +++ b/app/code/core/Mage/Tax/Model/Calculation/Rate.php @@ -43,12 +43,25 @@ protected function _construct() } /** - * Prepare location settings before save rate + * Prepare location settings and tax postcode before save rate * * @return Mage_Tax_Model_Calculation_Rate */ protected function _beforeSave() { + $zipIsRange = null; + $zipFrom = null; + $zipTo = null; + if (preg_match('/(\d+)\s*-\s*(\d+)/m', $this->getTaxPostcode(), $matchArr)) { + list(, $zipFrom, $zipTo) = $matchArr; + $this->setTaxPostcode("{$zipFrom}-{$zipTo}"); + $zipIsRange = 1; + } + $this + ->setZipFrom($zipFrom) + ->setZipTo($zipTo) + ->setZipIsRange($zipIsRange); + parent::_beforeSave(); $country = $this->getTaxCountryId(); $region = $this->getTaxRegionId(); @@ -129,8 +142,8 @@ public function deleteAllRates() } /** - * Load rate model by code - * + * Load rate model by code + * * @param string $code * @return Mage_Tax_Model_Calculation_Rate */ @@ -139,5 +152,5 @@ public function loadByCode($code) $this->load($code, 'code'); return $this; } - -} \ No newline at end of file + +} diff --git a/app/code/core/Mage/Tax/Model/Mysql4/Calculation.php b/app/code/core/Mage/Tax/Model/Mysql4/Calculation.php index 17d562d3b5..5f30280be4 100644 --- a/app/code/core/Mage/Tax/Model/Mysql4/Calculation.php +++ b/app/code/core/Mage/Tax/Model/Mysql4/Calculation.php @@ -72,7 +72,8 @@ public function getCalculationProcess($request, $rates = null) $ids = array(); $currentRate = 0; $totalPercent = 0; - for ($i=0; $igetPostCodeSubStringLength(); + $strlen = strlen($postcode); + if ($strlen > $len) { + $postcode = substr($postcode, 0, $len); + $strlen = $len; + } + + $strArr = array($postcode); + if ($strlen > 1) { + for ($i = 1; $i < $strlen; $i++) { + $strArr[] = sprintf('%s*', substr($postcode, 0, - $i)); + } + } + + return $strArr; + + } + + /** + * Load select and return tax rates + * + * @param Varien_Object $request + * @return array + */ protected function _getRates($request) { + $storeId = Mage::app()->getStore($request->getStore())->getId(); $select = $this->_getReadAdapter()->select(); @@ -143,24 +178,40 @@ protected function _getRates($request) $select->join(array('rule'=>$this->getTable('tax/tax_calculation_rule')), 'rule.tax_calculation_rule_id = main_table.tax_calculation_rule_id', array('rule.priority', 'rule.position')); $select->join(array('rate'=>$this->getTable('tax/tax_calculation_rate')), 'rate.tax_calculation_rate_id = main_table.tax_calculation_rate_id', array('value'=>'rate.rate', 'rate.tax_country_id', 'rate.tax_region_id', 'rate.tax_postcode', 'rate.tax_calculation_rate_id', 'rate.code')); + $select->joinLeft(array('title_table'=>$this->getTable('tax/tax_calculation_rate_title')), "rate.tax_calculation_rate_id = title_table.tax_calculation_rate_id AND title_table.store_id = '{$storeId}'", array('title'=>'IFNULL(title_table.value, rate.code)')); + $select ->where("rate.tax_country_id = ?", $request->getCountryId()) - ->where("rate.tax_region_id in ('*', '', ?)", $request->getRegionId()) - ->where("rate.tax_postcode in ('*', '', ?)", $request->getPostcode()); - - $select->joinLeft(array('title_table'=>$this->getTable('tax/tax_calculation_rate_title')), "rate.tax_calculation_rate_id = title_table.tax_calculation_rate_id AND title_table.store_id = '{$storeId}'", array('title'=>'IFNULL(title_table.value, rate.code)')); + ->where("rate.tax_region_id in ('*', '', ?)", $request->getRegionId()); $order = array('rule.priority ASC', 'rule.tax_calculation_rule_id ASC', 'rate.tax_country_id DESC', 'rate.tax_region_id DESC', 'rate.tax_postcode DESC', 'rate.rate DESC'); $select->order($order); + $selectClone = clone $select; + + $select + ->where("rate.zip_is_range IS NULL") + ->where("rate.tax_postcode in ('*', '', ?)", $this->_createSearchPostCodeTemplates($request->getPostcode())); + + $selectClone + ->where("rate.zip_is_range IS NOT NULL") + ->where("? BETWEEN rate.zip_from AND rate.zip_to", $request->getPostcode()); + + /** + * @see ZF-7592 issue http://framework.zend.com/issues/browse/ZF-7592 + */ + $select = $this->_getReadAdapter()->select()->union(array('(' . $select . ')', '(' . $selectClone . ')')); + return $this->_getReadAdapter()->fetchAll($select); + } protected function _calculateRate($rates) { $result = 0; $currentRate = 0; - for ($i=0; $i_getRates($request); - for ($i=0; $igetEvent()->getOrder(); - if (!$order->getConvertingFromQuote()) { + if (!$order->getConvertingFromQuote() || $order->getAppliedTaxIsSaved()) { return; } @@ -92,6 +92,7 @@ public function salesEventOrderAfterSave(Varien_Event_Observer $observer) Mage::getModel('sales/order_tax')->setData($data)->save(); } } + $order->setAppliedTaxIsSaved(true); } /** @@ -109,7 +110,7 @@ public function prepareCatalogIndexPriceSelect(Varien_Event_Observer $observer) $additionalCalculations = $response->getAdditionalCalculations(); $calculation = Mage::helper('tax')->getPriceTaxSql( - $table . '.value', 'IFNULL(tax_class_c.value, tax_class_d.value)' + $table . '.min_price', 'IFNULL(tax_class_c.value, tax_class_d.value)' ); if (!empty($calculation)) { diff --git a/app/code/core/Mage/Tax/Model/Sales/Total/Quote/Subtotal.php b/app/code/core/Mage/Tax/Model/Sales/Total/Quote/Subtotal.php index 50e2f8d716..a2d43ecaf1 100644 --- a/app/code/core/Mage/Tax/Model/Sales/Total/Quote/Subtotal.php +++ b/app/code/core/Mage/Tax/Model/Sales/Total/Quote/Subtotal.php @@ -73,7 +73,7 @@ public function __construct() */ public function collect(Mage_Sales_Model_Quote_Address $address) { - if ($this->_needSubtractTax($address)) { + if (!$address->getTaxSubtotalIsProcessed() && $this->_needSubtractTax($address)) { $address->setTotalAmount('subtotal', 0); $address->setBaseTotalAmount('subtotal', 0); $items = $address->getAllItems(); @@ -100,10 +100,11 @@ public function collect(Mage_Sales_Model_Quote_Address $address) $this->_config->setNeedUsePriceExcludeTax(true); } - if ($this->_needSubtractShippingTax($address)) { + if (!$address->getTaxSubtotalIsProcessed() && $this->_needSubtractShippingTax($address)) { $this->_processShippingAmount($address); $this->_config->setNeedUseShippingExcludeTax(true); } + $address->setTaxSubtotalIsProcessed(true); return $this; } diff --git a/app/code/core/Mage/Tax/Model/Sales/Total/Quote/Tax.php b/app/code/core/Mage/Tax/Model/Sales/Total/Quote/Tax.php index 6ebe04f988..c1f0b723e8 100644 --- a/app/code/core/Mage/Tax/Model/Sales/Total/Quote/Tax.php +++ b/app/code/core/Mage/Tax/Model/Sales/Total/Quote/Tax.php @@ -74,7 +74,14 @@ public function collect(Mage_Sales_Model_Quote_Address $address) { parent::collect($address); $store = $address->getQuote()->getStore(); - $address->setAppliedTaxes(array()); + $customer = $address->getQuote()->getCustomer(); + if ($customer) { + $this->_calculator->setCustomer($customer); + } + + if (!$address->getAppliedTaxesReset()) { + $address->setAppliedTaxes(array()); + } $items = $address->getAllItems(); if (!count($items)) { @@ -111,6 +118,8 @@ public function collect(Mage_Sales_Model_Quote_Address $address) $address->setBaseTotalAmount('subtotal', $baseSubtotal); } + $this->_addAmount($address->getExtraTaxAmount()); + $this->_addBaseAmount($address->getBaseExtraTaxAmount()); $this->_calculateShippingTax($address, $request); return $this; @@ -241,10 +250,10 @@ protected function _unitBaseCalculation(Mage_Sales_Model_Quote_Address $address, protected function _calcUnitTaxAmount(Mage_Sales_Model_Quote_Item_Abstract $item, $rate) { $store = $item->getStore(); - $price = $store->roundPrice($item->getCalculationPrice()); - $basePrice = $store->roundPrice($item->getBaseCalculationPrice()); - $origPrice = $store->roundPrice($item->getOriginalPrice()); - $baseOrigPrice = $store->roundPrice($item->getBaseOriginalPrice()); + $price = $store->roundPrice($item->getCalculationPrice()) + $item->getExtraTaxableAmount(); + $basePrice = $store->roundPrice($item->getBaseCalculationPrice()) + $item->getBaseExtraTaxableAmount(); + $origPrice = $store->roundPrice($item->getOriginalPrice()) + $item->getExtraTaxableAmount(); + $baseOrigPrice = $store->roundPrice($item->getBaseOriginalPrice()) + $item->getBaseExtraTaxableAmount(); $discountAmount = $item->getDiscountAmount(); $baseDiscountAmount = $item->getBaseDiscountAmount(); $qty = $item->getTotalQty(); @@ -369,6 +378,9 @@ protected function _calcRowTaxAmount($item, $rate) $subtotal = $item->getTotalQty()*$item->getOriginalPrice(); $baseSubtotal = $item->getTotalQty()*$item->getBaseOriginalPrice(); } + $subtotal = $subtotal + $item->getExtraRowTaxableAmount(); + $baseSubtotal = $baseSubtotal + $item->getBaseExtraRowTaxableAmount(); + $discountAmount = $item->getDiscountAmount(); $baseDiscountAmount = $item->getBaseDiscountAmount(); @@ -378,14 +390,14 @@ protected function _calcRowTaxAmount($item, $rate) $calculationSequence = $this->_helper->getCalculationSequence($store); switch ($calculationSequence) { case Mage_Tax_Model_Calculation::CALC_TAX_BEFORE_DISCOUNT_ON_EXCL: - $rowTax = $this->_calculator->calcTaxAmount($subtotal, $rate, false, false); - $baseRowTax = $this->_calculator->calcTaxAmount($baseSubtotal, $rate, false, false); + $rowTax = $this->_calculator->calcTaxAmount($subtotal, $rate, false); + $baseRowTax = $this->_calculator->calcTaxAmount($baseSubtotal, $rate, false); $calcTotal = $subtotal; $baseCalcTotal = $baseSubtotal; break; case Mage_Tax_Model_Calculation::CALC_TAX_BEFORE_DISCOUNT_ON_INCL: - $rowTax = $this->_calculator->calcTaxAmount($subtotal, $rate, false, false); - $baseRowTax = $this->_calculator->calcTaxAmount($baseSubtotal, $rate, false, false); + $rowTax = $this->_calculator->calcTaxAmount($subtotal, $rate, false); + $baseRowTax = $this->_calculator->calcTaxAmount($baseSubtotal, $rate, false); $calcTotal = $subtotal; $baseCalcTotal = $baseSubtotal; $item->setDiscountCalculationPrice(($subtotal+$rowTax)/$qty); @@ -393,8 +405,8 @@ protected function _calcRowTaxAmount($item, $rate) break; case Mage_Tax_Model_Calculation::CALC_TAX_AFTER_DISCOUNT_ON_EXCL: case Mage_Tax_Model_Calculation::CALC_TAX_AFTER_DISCOUNT_ON_INCL: - $rowTax = $this->_calculator->calcTaxAmount($subtotal-$discountAmount, $rate, false, false); - $baseRowTax = $this->_calculator->calcTaxAmount($baseSubtotal-$baseDiscountAmount, $rate, false, false); + $rowTax = $this->_calculator->calcTaxAmount($subtotal-$discountAmount, $rate, false); + $baseRowTax = $this->_calculator->calcTaxAmount($baseSubtotal-$baseDiscountAmount, $rate, false); $calcTotal = $subtotal-$discountAmount; $baseCalcTotal = $baseSubtotal-$baseDiscountAmount; break; @@ -481,8 +493,11 @@ protected function _aggregateTaxPerRate($item, $rate, &$taxGroups) $baseDiscountAmount = $item->getBaseDiscountAmount(); $qty = $item->getTotalQty(); $rateKey = (string) $rate; - $calcTotal = 0; - $baseCalcTotal = 0; + /** + * Add extra amounts which can be taxable too + */ + $calcTotal = $subtotal + $item->getExtraRowTaxableAmount(); + $baseCalcTotal = $baseSubtotal + $item->getBaseExtraRowTaxableAmount(); $item->setTaxPercent($rate); if (!isset($taxGroups[$rateKey]['totals'])) { @@ -495,25 +510,21 @@ protected function _aggregateTaxPerRate($item, $rate, &$taxGroups) $calculationSequence = $this->_helper->getCalculationSequence($store); switch ($calculationSequence) { case Mage_Tax_Model_Calculation::CALC_TAX_BEFORE_DISCOUNT_ON_EXCL: - $rowTax = $this->_calculator->calcTaxAmount($subtotal, $rate, false, false); - $baseRowTax = $this->_calculator->calcTaxAmount($baseSubtotal, $rate, false, false); - $calcTotal = $subtotal; - $baseCalcTotal = $baseSubtotal; + $rowTax = $this->_calculator->calcTaxAmount($calcTotal, $rate, false, false); + $baseRowTax = $this->_calculator->calcTaxAmount($baseCalcTotal, $rate, false, false); break; case Mage_Tax_Model_Calculation::CALC_TAX_BEFORE_DISCOUNT_ON_INCL: - $rowTax = $this->_calculator->calcTaxAmount($subtotal, $rate, false, false); - $baseRowTax = $this->_calculator->calcTaxAmount($baseSubtotal, $rate, false, false); - $calcTotal = $subtotal; - $baseCalcTotal = $baseSubtotal; + $rowTax = $this->_calculator->calcTaxAmount($calcTotal, $rate, false, false); + $baseRowTax = $this->_calculator->calcTaxAmount($baseCalcTotal, $rate, false, false); $item->setDiscountCalculationPrice(($subtotal+$rowTax)/$qty); $item->setBaseDiscountCalculationPrice(($baseSubtotal+$baseRowTax)/$qty); break; case Mage_Tax_Model_Calculation::CALC_TAX_AFTER_DISCOUNT_ON_EXCL: case Mage_Tax_Model_Calculation::CALC_TAX_AFTER_DISCOUNT_ON_INCL: - $rowTax = $this->_calculator->calcTaxAmount($subtotal-$discountAmount, $rate, false, false); - $baseRowTax = $this->_calculator->calcTaxAmount($baseSubtotal-$baseDiscountAmount, $rate, false, false); - $calcTotal = $subtotal-$discountAmount; - $baseCalcTotal = $baseSubtotal-$baseDiscountAmount; + $calcTotal = $calcTotal-$discountAmount; + $baseCalcTotal = $baseCalcTotal-$baseDiscountAmount; + $rowTax = $this->_calculator->calcTaxAmount($calcTotal, $rate, false, false); + $baseRowTax = $this->_calculator->calcTaxAmount($baseCalcTotal, $rate, false, false); break; } @@ -538,6 +549,12 @@ protected function _aggregateTaxPerRate($item, $rate, &$taxGroups) return $this; } + /** + * Recalculate parent item amounts base on children data + * + * @param Mage_Sales_Model_Quote_Item_Abstract $item + * @return Mage_Tax_Model_Sales_Total_Quote + */ protected function _recalculateParent(Mage_Sales_Model_Quote_Item_Abstract $item) { $calculationPrice = 0; @@ -678,7 +695,7 @@ public function processConfigArray($config, $store) default: $config['after'][] = 'discount'; break; - } - return $config; + } + return $config; } } \ No newline at end of file diff --git a/app/code/core/Mage/Tax/etc/adminhtml.xml b/app/code/core/Mage/Tax/etc/adminhtml.xml new file mode 100644 index 0000000000..31a59ad1f7 --- /dev/null +++ b/app/code/core/Mage/Tax/etc/adminhtml.xml @@ -0,0 +1,111 @@ + + + + + + + + Tax + 500 + + + Manage Tax Rules + adminhtml/tax_rule + + + Manage Tax Zones & Rates + adminhtml/tax_rate + + + Import / Export Tax Rates + adminhtml/tax_rate/importExport + + + Customer Tax Classes + adminhtml/tax_class_customer + + + Product Tax Classes + adminhtml/tax_class_product + + + + + + + + + + + + + + Tax + 500 + + + Customer Tax Classes + 0 + + + Product Tax Classes + 10 + + + Import / Export Tax Rates + 20 + + + Manage Tax Zones & Rates + 30 + + + Manage Tax Rules + 40 + + + + + + + + + + + Tax Section + 55 + + + + + + + + + + diff --git a/app/code/core/Mage/Tax/etc/config.xml b/app/code/core/Mage/Tax/etc/config.xml index 4c7d4106b1..948cec90c1 100644 --- a/app/code/core/Mage/Tax/etc/config.xml +++ b/app/code/core/Mage/Tax/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.9 + 0.7.10 @@ -64,20 +64,7 @@ Mage_Tax Mage_Tax_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - @@ -99,7 +86,6 @@ - singleton tax/observer salesEventConvertQuoteAddressToOrder @@ -108,7 +94,6 @@ - singleton tax/observer salesEventOrderAfterSave @@ -117,7 +102,6 @@ - singleton tax/observer prepareCatalogIndexPriceSelect @@ -126,7 +110,6 @@ - singleton tax/observer addTaxPercentToProductCollection @@ -189,88 +172,6 @@ - - - - - Tax - 500 - - - Manage Tax Rules - adminhtml/tax_rule - - - Manage Tax Zones & Rates - adminhtml/tax_rate - - - Import / Export Tax Rates - adminhtml/tax_rate/importExport - - - Customer Tax Classes - adminhtml/tax_class_customer - - - Product Tax Classes - adminhtml/tax_class_product - - - - - - - - - - - - - - Tax - 500 - - - Customer Tax Classes - 0 - - - Product Tax Classes - 10 - - - Import / Export Tax Rates - 20 - - - Manage Tax Zones & Rates - 30 - - - Manage Tax Rules - 40 - - - - - - - - - - - Tax Section - 55 - - - - - - - - - diff --git a/app/code/core/Mage/Tax/sql/tax_setup/mysql4-upgrade-0.7.9-0.7.10.php b/app/code/core/Mage/Tax/sql/tax_setup/mysql4-upgrade-0.7.9-0.7.10.php new file mode 100644 index 0000000000..1e58650c25 --- /dev/null +++ b/app/code/core/Mage/Tax/sql/tax_setup/mysql4-upgrade-0.7.9-0.7.10.php @@ -0,0 +1,40 @@ +startSetup(); + +$table = $installer->getTable('tax_calculation_rate'); + +$installer->getConnection()->addColumn($table, 'zip_is_range', "TINYINT(1) DEFAULT NULL"); +$installer->getConnection()->addColumn($table, 'zip_from', "VARCHAR(10) DEFAULT NULL"); +$installer->getConnection()->addColumn($table, 'zip_to', "VARCHAR(10) DEFAULT NULL"); + +$installer->getConnection()->addKey($table, 'IDX_TAX_CALCULATION_RATE_RANGE', array('tax_calculation_rate_id', 'tax_country_id', 'tax_region_id', 'zip_is_range', 'tax_postcode')); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Usa/etc/config.xml b/app/code/core/Mage/Usa/etc/config.xml index 3ee75f858f..b20f10d52d 100644 --- a/app/code/core/Mage/Usa/etc/config.xml +++ b/app/code/core/Mage/Usa/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.0 + 0.7.1 @@ -37,15 +37,7 @@ Mage_Usa - - directory_setup - - - - core_read - - @@ -158,7 +150,7 @@ RES GND http://www.ups.com/using/services/rave/qcostcgi.cgi - https://www.ups.com/ups.app/xml/Rate + https://onlinetools.ups.com/ups.app/xml/Rate 0 usa/shipping_carrier_ups CC diff --git a/app/code/core/Mage/Usa/sql/usa_setup/mysql4-upgrade-0.7.0-0.7.1.php b/app/code/core/Mage/Usa/sql/usa_setup/mysql4-upgrade-0.7.0-0.7.1.php new file mode 100644 index 0000000000..008857bb70 --- /dev/null +++ b/app/code/core/Mage/Usa/sql/usa_setup/mysql4-upgrade-0.7.0-0.7.1.php @@ -0,0 +1,34 @@ +run("UPDATE {$installer->getTable('core/config_data')} SET `value` = REPLACE(`value`, + 'https://www.ups.com/ups.app/xml/Rate', 'https://onlinetools.ups.com/ups.app/xml/Rate' + ) WHERE `path` = 'carriers/ups/gateway_xml_url'" +); diff --git a/app/code/core/Mage/Weee/Helper/Data.php b/app/code/core/Mage/Weee/Helper/Data.php index 74004e6115..55250f0f82 100644 --- a/app/code/core/Mage/Weee/Helper/Data.php +++ b/app/code/core/Mage/Weee/Helper/Data.php @@ -34,86 +34,93 @@ class Mage_Weee_Helper_Data extends Mage_Core_Helper_Abstract protected $_storeDisplayConfig = array(); + /** + * Get weee amount display type on product view page + * + * @param mixed $store + * @return int + */ public function getPriceDisplayType($store = null) { - if (!is_null($store)) { - if ($store instanceof Mage_Core_Model_Store) { - $key = $store->getId(); - } else { - $key = $store; - } - } else { - $key = 'current'; - } - - if (!isset($this->_storeDisplayConfig[$key])) { - $value = Mage::getStoreConfig('tax/weee/display', $store); - $this->_storeDisplayConfig[$key] = $value; - } - - return $this->_storeDisplayConfig[$key]; + return Mage::getStoreConfig('tax/weee/display', $store); } + /** + * Get weee amount display type on product list page + * + * @param mixed $store + * @return int + */ public function getListPriceDisplayType($store = null) { - if (!is_null($store)) { - if ($store instanceof Mage_Core_Model_Store) { - $key = $store->getId(); - } else { - $key = $store; - } - } else { - $key = 'current'; - } - - if (!isset($this->_storeDisplayConfig[$key])) { - $value = Mage::getStoreConfig('tax/weee/display_list', $store); - $this->_storeDisplayConfig[$key] = $value; - } - - return $this->_storeDisplayConfig[$key]; + return Mage::getStoreConfig('tax/weee/display_list', $store); } + /** + * Get weee amount display type in sales modules + * + * @param mixed $store + * @return int + */ public function getSalesPriceDisplayType($store = null) { - if (!is_null($store)) { - if ($store instanceof Mage_Core_Model_Store) { - $key = $store->getId(); - } else { - $key = $store; - } - } else { - $key = 'current'; - } - - if (!isset($this->_storeDisplayConfig[$key])) { - $value = Mage::getStoreConfig('tax/weee/display_sales', $store); - $this->_storeDisplayConfig[$key] = $value; - } - - return $this->_storeDisplayConfig[$key]; + return Mage::getStoreConfig('tax/weee/display_sales', $store); } + /** + * Get weee amount display type in email templates + * + * @param mixed $store + * @return int + */ public function getEmailPriceDisplayType($store = null) { - if (!is_null($store)) { - if ($store instanceof Mage_Core_Model_Store) { - $key = $store->getId(); - } else { - $key = $store; - } - } else { - $key = 'current'; - } + return Mage::getStoreConfig('tax/weee/display_email', $store); + } - if (!isset($this->_storeDisplayConfig[$key])) { - $value = Mage::getStoreConfig('tax/weee/display_email', $store); - $this->_storeDisplayConfig[$key] = $value; - } + /** + * Check if weee tax amount should be discounted + * + * @param mixed $store + * @return bool + */ + public function isDiscounted($store = null) + { + return Mage::getStoreConfigFlag('tax/weee/discount', $store); + } - return $this->_storeDisplayConfig[$key]; + /** + * Check if weee tax amount should be taxable + * + * @param mixed $store + * @return bool + */ + public function isTaxable($store = null) + { + return Mage::getStoreConfigFlag('tax/weee/apply_vat', $store); } + /** + * Check if weee tax amount should be included to subtotal + * + * @param mixed $store + * @return bool + */ + public function includeInSubtotal($store = null) + { + return Mage::getStoreConfigFlag('tax/weee/include_in_subtotal', $store); + } + + /** + * Get weee tax amount for product based on shipping and billing addresses, website and tax settings + * + * @param Mage_Catalog_Model_Product $product + * @param null|Mage_Customer_Model_Address_Abstract $shipping + * @param null|Mage_Customer_Model_Address_Abstract $billing + * @param mixed $website + * @param bool $calculateTaxes + * @return float + */ public function getAmount($product, $shipping = null, $billing = null, $website = null, $calculateTaxes = false) { if ($this->isEnabled()) { @@ -125,30 +132,29 @@ public function getAmount($product, $shipping = null, $billing = null, $website public function typeOfDisplay($product, $compareTo = null, $zone = null, $store = null) { $type = 0; + if (!$this->isEnabled($store)) { + return false; + } switch ($zone) { case 'product_view': - $type = $this->getPriceDisplayType($store); - break; - + $type = $this->getPriceDisplayType($store); + break; case 'product_list': - $type = $this->getListPriceDisplayType($store); - break; - + $type = $this->getListPriceDisplayType($store); + break; case 'sales': - $type = $this->getSalesPriceDisplayType($store); - break; - + $type = $this->getSalesPriceDisplayType($store); + break; case 'email': - $type = $this->getEmailPriceDisplayType($store); - break; - + $type = $this->getEmailPriceDisplayType($store); + break; default: - if (Mage::registry('current_product')) { - $type = $this->getPriceDisplayType($store); - } else { - $type = $this->getListPriceDisplayType($store); - } - break; + if (Mage::registry('current_product')) { + $type = $this->getPriceDisplayType($store); + } else { + $type = $this->getListPriceDisplayType($store); + } + break; } if (is_null($compareTo)) { @@ -199,21 +205,6 @@ public function setApplied($item, $value) return $this; } - public function isDiscounted($store = null) - { - return Mage::getStoreConfigFlag('tax/weee/discount', $store); - } - - public function isTaxable($store = null) - { - return Mage::getStoreConfigFlag('tax/weee/apply_vat', $store); - } - - public function includeInSubtotal($store = null) - { - return Mage::getStoreConfigFlag('tax/weee/include_in_subtotal', $store); - } - public function getProductWeeeAttributesForDisplay($product) { if ($this->isEnabled()) { diff --git a/app/code/core/Mage/Weee/Model/Config/Source/Display.php b/app/code/core/Mage/Weee/Model/Config/Source/Display.php index 09d27ec9bd..4268711729 100644 --- a/app/code/core/Mage/Weee/Model/Config/Source/Display.php +++ b/app/code/core/Mage/Weee/Model/Config/Source/Display.php @@ -28,12 +28,27 @@ class Mage_Weee_Model_Config_Source_Display public function toOptionArray() { + /** + * VAT is not applicable to FPT separately (we can't have FPT incl/excl VAT) + */ return array( - array('value'=>0, 'label'=>Mage::helper('weee')->__('Including FPT only')), - array('value'=>1, 'label'=>Mage::helper('weee')->__('Including FPT and FPT description [excl. FPT VAT]')), - array('value'=>4, 'label'=>Mage::helper('weee')->__('Including FPT and FPT description [incl. FPT VAT]')), - array('value'=>2, 'label'=>Mage::helper('weee')->__('Excluding FPT, FPT description, final price')), - array('value'=>3, 'label'=>Mage::helper('weee')->__('Excluding FPT')), + array( + 'value' => 0, + 'label' => Mage::helper('weee')->__('Including FPT only') + ), + array( + 'value' => 1, + 'label' => Mage::helper('weee')->__('Including FPT and FPT description') + ), + //array('value'=>4, 'label'=>Mage::helper('weee')->__('Including FPT and FPT description [incl. FPT VAT]')), + array( + 'value' => 2, + 'label' => Mage::helper('weee')->__('Excluding FPT, FPT description, final price') + ), + array( + 'value' => 3, + 'label' => Mage::helper('weee')->__('Excluding FPT') + ), ); } diff --git a/app/code/core/Mage/Weee/Model/Mysql4/Tax.php b/app/code/core/Mage/Weee/Model/Mysql4/Tax.php index 146425f793..814a4ed005 100644 --- a/app/code/core/Mage/Weee/Model/Mysql4/Tax.php +++ b/app/code/core/Mage/Weee/Model/Mysql4/Tax.php @@ -1,10 +1,30 @@ _init('weee/tax', 'value_id'); } - + public function fetchOne($select) { return $this->_getReadAdapter()->fetchOne($select); @@ -17,27 +37,68 @@ public function fetchCol($select) public function updateDiscountPercents() { - $this->_getWriteAdapter()->delete($this->getTable('weee/discount')); + return $this->_updateDiscountPercents(); + } + + public function updateProductsDiscountPercent($condition) + { + return $this->_updateDiscountPercents($condition); + } + + /** + * Update tax percents for WEEE based on products condition + * + * @param mixed $productCondition + * @return Mage_Weee_Model_Mysql4_Tax + */ + protected function _updateDiscountPercents($productCondition = null) + { $now = strtotime(now()); $select = $this->_getReadAdapter()->select(); - $select->from(array('data'=>$this->getTable('catalogrule/rule_product'))) - ->where('(from_time <= ? OR from_time = 0)', $now) - ->where('(to_time >= ? OR to_time = 0)', $now) - ->order(array('data.website_id', 'data.customer_group_id', 'data.product_id', 'data.sort_order')); + $select->from(array('data'=>$this->getTable('catalogrule/rule_product'))); + + $deleteCondition = ''; + if ($productCondition) { + if ($productCondition instanceof Mage_Catalog_Model_Product) { + $select->where('product_id=?', $productCondition->getId()); + $deleteCondition = $this->_getWriteAdapter()->quoteInto('entity_id=?', $productCondition->getId()); + } elseif ($productCondition instanceof Mage_Catalog_Model_Product_Condition_Interface) { + $productCondition = $productCondition->getIdsSelect($this->_getWriteAdapter())->__toString(); + $select->where('product_id IN ('.$productCondition.')'); + $deleteCondition = 'entity_id IN ('.$productCondition.')'; + } else { + $select->where('product_id=?', $productCondition); + $deleteCondition = $this->_getWriteAdapter()->quoteInto('entity_id=?', $productCondition); + } + } else { + $select->where('(from_time <= ? OR from_time = 0)', $now) + ->where('(to_time >= ? OR to_time = 0)', $now); + } + $this->_getWriteAdapter()->delete($this->getTable('weee/discount'), $deleteCondition); + + $select->order(array('data.website_id', 'data.customer_group_id', 'data.product_id', 'data.sort_order')); + + $data = $this->_getReadAdapter()->query($select); - $data = $this->_getReadAdapter()->fetchAll($select); $productData = array(); $stops = array(); - foreach ($data as $row) { + $prevKey = false; + while ($row = $data->fetch()) { $key = "{$row['product_id']}-{$row['website_id']}-{$row['customer_group_id']}"; if (isset($stops[$key]) && $stops[$key]) { continue; } + if ($prevKey && ($prevKey != $key)) { + foreach ($productData as $product) { + $this->_getWriteAdapter()->insert($this->getTable('weee/discount'), $product); + } + $productData = array(); + } if ($row['action_operator'] == 'by_percent') { if (isset($productData[$key])) { - $productData[$key]['value'] += $productData[$key]['value']/100*$row['action_amount']; + $productData[$key]['value'] -= $productData[$key]['value']/100*$row['action_amount']; } else { $productData[$key] = array( 'entity_id' => $row['product_id'], @@ -45,17 +106,18 @@ public function updateDiscountPercents() 'website_id' => $row['website_id'], 'value' => 100-max(0, min(100, $row['action_amount'])), ); - } + } } if ($row['action_stop']) { $stops[$key] = true; } + $prevKey = $key; } - foreach ($productData as $product) { $this->_getWriteAdapter()->insert($this->getTable('weee/discount'), $product); } + return $this; } public function getProductDiscountPercent($product, $website, $group) diff --git a/app/code/core/Mage/Weee/Model/Observer.php b/app/code/core/Mage/Weee/Model/Observer.php index 05147f0972..ce545e2f26 100644 --- a/app/code/core/Mage/Weee/Model/Observer.php +++ b/app/code/core/Mage/Weee/Model/Observer.php @@ -26,6 +26,11 @@ class Mage_Weee_Model_Observer extends Mage_Core_Model_Abstract { + /** + * Assign custom renderer for product create/edit form weee attribute element + * + * @param Varien_Event_Observer $observer + */ public function setWeeeRendererInForm(Varien_Event_Observer $observer) { //adminhtml_catalog_product_edit_prepare_form @@ -43,6 +48,11 @@ public function setWeeeRendererInForm(Varien_Event_Observer $observer) } } + /** + * Exclude WEEE attributes from standard form generation + * + * @param Varien_Event_Observer $observer + */ public function updateExcludedFieldList(Varien_Event_Observer $observer) { //adminhtml_catalog_product_form_prepare_excluded_field_list @@ -56,6 +66,11 @@ public function updateExcludedFieldList(Varien_Event_Observer $observer) $block->setFormExcludedFieldList($list); } + /** + * Add additional price calculation to select object which is using for select indexed data + * + * @param Varien_Event_Observer $observer + */ public function prepareCatalogIndexSelect(Varien_Event_Observer $observer) { if (!Mage::helper('weee')->isEnabled($observer->getEvent()->getStoreId())) { @@ -63,14 +78,14 @@ public function prepareCatalogIndexSelect(Varien_Event_Observer $observer) } switch(Mage::helper('weee')->getListPriceDisplayType()) { - case 2: - case 3: + case Mage_Weee_Model_Tax::DISPLAY_EXCL_DESCR_INCL: + case Mage_Weee_Model_Tax::DISPLAY_EXCL: return $this; } $select = $observer->getEvent()->getSelect(); - $table = $observer->getEvent()->getTable(); - $storeId = $observer->getEvent()->getStoreId(); + $table = $observer->getEvent()->getTable(); + $storeId= $observer->getEvent()->getStoreId(); $websiteId = Mage::app()->getStore($storeId)->getWebsiteId(); $customerGroupId = Mage::getSingleton('customer/session')->getCustomerGroupId(); @@ -133,11 +148,21 @@ public function prepareCatalogIndexSelect(Varien_Event_Observer $observer) } } + /** + * Get empty select object + * + * @return Varien_Db_Select + */ protected function _getSelect() { return Mage::getSingleton('weee/tax')->getResource()->getReadConnection()->select(); } + /** + * Add new attribute type to manage attributes interface + * + * @param Varien_Event_Observer $observer + */ public function addWeeeTaxAttributeType(Varien_Event_Observer $observer) { // adminhtml_product_attribute_types @@ -165,6 +190,11 @@ public function addWeeeTaxAttributeType(Varien_Event_Observer $observer) $response->setTypes($types); } + /** + * Automaticaly assign backend model to weee attributes + * + * @param Varien_Event_Observer $observer + */ public function assignBackendModelToAttribute(Varien_Event_Observer $observer) { $backendModel = 'weee/attribute_backend_weee_tax'; @@ -184,6 +214,11 @@ public function assignBackendModelToAttribute(Varien_Event_Observer $observer) } } + /** + * Add custom element type for attributes form + * + * @param Varien_Event_Observer $observer + */ public function updateElementTypes(Varien_Event_Observer $observer) { $response = $observer->getEvent()->getResponse(); @@ -193,12 +228,33 @@ public function updateElementTypes(Varien_Event_Observer $observer) return $this; } + /** + * Update WEEE amounts discount percents + * + * @param Varien_Event_Observer $observer + * @return Mage_Weee_Model_Observer + */ public function updateDiscountPercents(Varien_Event_Observer $observer) { - Mage::getModel('weee/tax')->updateDiscountPercents(); + if (!Mage::helper('weee')->isEnabled()) { + return $this; + } + + $eventProduct = $observer->getEvent()->getProduct(); + $productCondition = $observer->getEvent()->getProductCondition(); + if ($productCondition) { + $eventProduct = $productCondition; + } + Mage::getModel('weee/tax')->updateProductsDiscountPercent($eventProduct); return $this; } + /** + * Update configurable options of the product view page + * + * @param Varien_Event_Observer $observer + * @return Mage_Weee_Model_Observer + */ public function updateCofigurableProductOptions(Varien_Event_Observer $observer) { if (!Mage::helper('weee')->isEnabled()) { @@ -226,6 +282,12 @@ public function updateCofigurableProductOptions(Varien_Event_Observer $observer) return $this; } + /** + * Process bundle options selection for prepare view json + * + * @param Varien_Event_Observer $observer + * @return Mage_Weee_Model_Observer + */ public function updateBundleProductOptions(Varien_Event_Observer $observer) { if (!Mage::helper('weee')->isEnabled()) { diff --git a/app/code/core/Mage/Weee/Model/Tax.php b/app/code/core/Mage/Weee/Model/Tax.php index f8c11f80b9..c0327fee8f 100644 --- a/app/code/core/Mage/Weee/Model/Tax.php +++ b/app/code/core/Mage/Weee/Model/Tax.php @@ -20,14 +20,35 @@ class Mage_Weee_Model_Tax extends Mage_Core_Model_Abstract { + /** + * Including FPT only + */ + const DISPLAY_INCL = 0; + /** + * Including FPT and FPT description + */ + const DISPLAY_INCL_DESCR = 1; + /** + * Excluding FPT, FPT description, final price + */ + const DISPLAY_EXCL_DESCR_INCL = 2; + /** + * Excluding FPT + */ + const DISPLAY_EXCL = 3; + protected $_allAttributes = null; protected $_productDiscounts = array(); + /** + * Initialize resource + */ protected function _construct() { $this->_init('weee/tax', 'weee/tax'); } + public function getWeeeAmount($product, $shipping = null, $billing = null, $website = null, $calculateTax = false, $ignoreDiscount = false) { $amount = 0; @@ -66,14 +87,20 @@ public function getProductWeeeAttributes($product, $shipping = null, $billing = $websiteId = Mage::app()->getWebsite($website)->getId(); $store = Mage::app()->getWebsite($website)->getDefaultGroup()->getDefaultStore(); + $customer = null; if ($shipping) { $customerTaxClass = $shipping->getQuote()->getCustomerTaxClassId(); + $customer = $shipping->getQuote()->getCustomer(); } else { $customerTaxClass = null; } - $rateRequest = Mage::getModel('tax/calculation')->getRateRequest($shipping, $billing, $customerTaxClass, $store); - $defaultRateRequest = Mage::getModel('tax/calculation')->getRateRequest(false, false, false, $store); + $calculator = Mage::getModel('tax/calculation'); + if ($customer) { + $calculator->setCustomer($customer); + } + $rateRequest = $calculator->getRateRequest($shipping, $billing, $customerTaxClass, $store); + $defaultRateRequest = $calculator->getRateRequest(false, false, false, $store); $discountPercent = 0; if (!$ignoreDiscount && Mage::helper('weee')->isDiscounted($store)) { @@ -115,14 +142,16 @@ public function getProductWeeeAttributes($product, $shipping = null, $billing = $taxAmount = $amount = 0; $amount = $value; - - if ($calculateTax && Mage::helper('weee')->isTaxable($store)) { - $defaultPercent = Mage::getModel('tax/calculation')->getRate($defaultRateRequest->setProductClassId($product->getTaxClassId())); - $currentPercent = $product->getTaxPercent(); - - $taxAmount = Mage::app()->getStore()->roundPrice($value/(100+$defaultPercent)*$currentPercent); - $amount = $value - $taxAmount; - } + /** + * We can't use FPT imcluding/excluding tax + */ +// if ($calculateTax && Mage::helper('weee')->isTaxable($store)) { +// $defaultPercent = Mage::getModel('tax/calculation')->getRate($defaultRateRequest->setProductClassId($product->getTaxClassId())); +// $currentPercent = $product->getTaxPercent(); +// +// $taxAmount = Mage::app()->getStore()->roundPrice($value/(100+$defaultPercent)*$currentPercent); +// $amount = $value - $taxAmount; +// } $one = new Varien_Object(); $one->setName(Mage::helper('catalog')->__($attribute->getFrontend()->getLabel())) @@ -152,9 +181,26 @@ protected function _getDiscountPercentForProduct($product) } } + /** + * Update discounts for FPT amounts of all products + * + * @return Mage_Weee_Model_Tax + */ public function updateDiscountPercents() { $this->getResource()->updateDiscountPercents(); return $this; } + + /** + * Update discounts for FPT amounts base on products condiotion + * + * @param mixed $products + * @return Mage_Weee_Model_Tax + */ + public function updateProductsDiscountPercent($products) + { + $this->getResource()->updateProductsDiscountPercent($products); + return $this; + } } diff --git a/app/code/core/Mage/Weee/Model/Total/Quote/Weee.php b/app/code/core/Mage/Weee/Model/Total/Quote/Weee.php index 45506d1be4..98f9c34f04 100644 --- a/app/code/core/Mage/Weee/Model/Total/Quote/Weee.php +++ b/app/code/core/Mage/Weee/Model/Total/Quote/Weee.php @@ -25,61 +25,311 @@ */ -class Mage_Weee_Model_Total_Quote_Weee extends Mage_Sales_Model_Quote_Address_Total_Tax +class Mage_Weee_Model_Total_Quote_Weee extends Mage_Tax_Model_Sales_Total_Quote_Tax { - public function __construct(){ + /** + * Weee module helper object + * + * @var Mage_Weee_Helper_Data + */ + protected $_helper; + protected $_store; + + /** + * Flag which notify what tax amount can be affected by fixed porduct tax + * + * @var bool + */ + protected $_isTaxAffected; + + /** + * Initialize Weee totals collector + */ + public function __construct() + { $this->setCode('weee'); + $this->_helper = Mage::helper('weee'); } + /** + * Collect Weee taxes amount ant prepare items prices for taxation and discount + * + * @param Mage_Sales_Model_Quote_Address $address + * @return Mage_Weee_Model_Total_Quote_Weee + */ public function collect(Mage_Sales_Model_Quote_Address $address) { - $totalWeeeTax = 0; - $baseTotalWeeeTax = 0; - + Mage_Sales_Model_Quote_Address_Total_Abstract::collect($address); + $this->_isTaxAffected = false; $items = $address->getAllItems(); if (!count($items)) { return $this; } + $address->setAppliedTaxesReset(true); + $address->setAppliedTaxes(array()); + + $this->_store = $address->getQuote()->getStore(); foreach ($items as $item) { if ($item->getParentItemId()) { continue; } - $this->_resetItemData($item); - if ($item->getHasChildren() && $item->isChildrenCalculated()) { foreach ($item->getChildren() as $child) { $this->_resetItemData($child); - $this->_processItem($address, $child, true); - - $totalWeeeTax += $child->getWeeeTaxAppliedRowAmount(); - $baseTotalWeeeTax += $child->getBaseWeeeTaxAppliedRowAmount(); + $this->_process($address, $child); } + $this->_recalculateParent($item); } else { - $this->_processItem($address, $item); - - $totalWeeeTax += $item->getWeeeTaxAppliedRowAmount(); - $baseTotalWeeeTax += $item->getBaseWeeeTaxAppliedRowAmount(); + $this->_process($address, $item); } } - $address->setGrandTotal($address->getGrandTotal() + $totalWeeeTax); - $address->setBaseGrandTotal($address->getBaseGrandTotal() + $baseTotalWeeeTax); + if ($this->_isTaxAffected) { + $address->unsSubtotalInclTax(); + $address->unsBaseSubtotalInclTax(); + } + + return $this; + } + + /** + * Calculate item fixed tax and prepare information for discount and recular taxation + * + * @param Mage_Sales_Model_Quote_Address $address + * @param Mage_Sales_Model_Quote_Item_Abstract $item + * @return Mage_Weee_Model_Total_Quote_Weee + */ + protected function _process(Mage_Sales_Model_Quote_Address $address, $item) + { + if (!$this->_helper->isEnabled($this->_store)) { + return $this; + } + + $attributes = $this->_helper->getProductWeeeAttributes( + $item->getProduct(), + $address, + $address->getQuote()->getBillingAddress(), + $this->_store->getWebsiteId() + ); + + $applied = array(); + $productTaxes = array(); + + $totalValue = 0; + $baseTotalValue = 0; + $totalRowValue = 0; + $baseTotalRowValue = 0; + + foreach ($attributes as $k=>$attribute) { + $baseValue = $attribute->getAmount(); + $value = $this->_store->convertPrice($baseValue); + $rowValue = $value*$item->getTotalQty(); + $baseRowValue = $baseValue*$item->getTotalQty(); + $title = $attribute->getName(); + + $totalValue += $value; + $baseTotalValue += $baseValue; + $totalRowValue += $rowValue; + $baseTotalRowValue += $baseRowValue; + + $productTaxes[] = array( + 'title' => $title, + 'base_amount' => $baseValue, + 'amount' => $value, + 'row_amount' => $rowValue, + 'base_row_amount'=> $baseRowValue, + /** + * Tax value can't be presented as include/exclude tax + */ + 'base_amount_incl_tax' => $baseValue, + 'amount_incl_tax' => $value, + 'row_amount_incl_tax' => $rowValue, + 'base_row_amount_incl_tax' => $baseRowValue, + ); + + $applied[] = array( + 'id' => $attribute->getCode(), + 'percent' => null, + 'hidden' => $this->_helper->includeInSubtotal($this->_store), + 'rates' => array(array( + 'base_real_amount'=> $baseRowValue, + 'base_amount' => $baseRowValue, + 'amount' => $rowValue, + 'code' => $attribute->getCode(), + 'title' => $title, + 'percent' => null, + 'position' => 1, + 'priority' => -1000+$k, + )) + ); + } + + $item->setWeeeTaxAppliedAmount($totalValue) + ->setBaseWeeeTaxAppliedAmount($baseTotalValue) + ->setWeeeTaxAppliedRowAmount($totalRowValue) + ->setBaseWeeeTaxAppliedRowAmount($baseTotalRowValue); + + $this->_processTaxSettings($item, $totalValue, $baseTotalValue, $totalRowValue, $baseTotalRowValue) + ->_processTotalAmount($address, $totalRowValue, $baseTotalRowValue) + ->_processDiscountSettings($item, $totalValue, $baseTotalValue); + + $this->_helper->setApplied($item, array_merge($this->_helper->getApplied($item), $productTaxes)); + if ($applied) { + $this->_saveAppliedTaxes($address, $applied, + $item->getWeeeTaxAppliedAmount(), + $item->getBaseWeeeTaxAppliedAmount(), + null + ); + } + + } + + /** + * Check if discount should be applied to weee and add weee to discounted price + * + * @param Mage_Sales_Model_Quote_Item_Abstract $item + * @param float $value + * @param float $baseValue + * @return Mage_Weee_Model_Total_Quote_Weee + */ + protected function _processDiscountSettings($item, $value, $baseValue) + { + if ($this->_helper->isDiscounted($this->_store)) { + Mage::helper('salesrule')->addItemDiscountPrices($item, $baseValue, $value); + } + return $this; + } + + /** + * Add extra amount which should be taxable by regular tax + * + * @param Mage_Sales_Model_Quote_Item_Abstract $item + * @param float $value + * @param float $baseValue + * @param float $rowValue + * @param float $baseRowValue + * @return Mage_Weee_Model_Total_Quote_Weee + */ + protected function _processTaxSettings($item, $value, $baseValue, $rowValue, $baseRowValue) + { + if ($this->_helper->isTaxable($this->_store) && $rowValue) { + $item->setExtraTaxableAmount($value) + ->setBaseExtraTaxableAmount($baseValue) + ->setExtraRowTaxableAmount($rowValue) + ->setBaseExtraRowTaxableAmount($baseRowValue) + ->unsRowTotalInclTax() + ->unsBaseRowTotalInclTax() + ->unsPriceInclTax() + ->unsBasePriceInclTax(); + $this->_isTaxAffected = true; + } return $this; } + /** + * Proces row amount based on FPT total amount configuration setting + * + * @param Mage_Sales_Model_Quote_Address $address + * @param float $rowValue + * @param float $baseRowValue + * @return Mage_Weee_Model_Total_Quote_Weee + */ + protected function _processTotalAmount($address, $rowValue, $baseRowValue) + { + if ($this->_helper->includeInSubtotal($this->_store)) { + $address->addTotalAmount('subtotal', $rowValue); + $address->addBaseTotalAmount('subtotal', $baseRowValue); + $this->_isTaxAffected = true; + } else { + $address->setExtraTaxAmount($address->getExtraTaxAmount() + $rowValue); + $address->setBaseExtraTaxAmount($address->getBaseExtraTaxAmount() + $baseRowValue); + } + return $this; + } + + /** + * Recalculate parent item amounts based on children results + * + * @param Mage_Sales_Model_Quote_Item_Abstract $item + * @return Mage_Weee_Model_Total_Quote_Weee + */ + protected function _recalculateParent(Mage_Sales_Model_Quote_Item_Abstract $item) + { + + } + + /** + * Reset information about FPT for shopping cart item + * + * @param Mage_Sales_Model_Quote_Item_Abstract $item + * @return Mage_Weee_Model_Total_Quote_Weee + */ + protected function _resetItemData($item) + { + $this->_helper->setApplied($item, array()); + + $item->setBaseWeeeTaxDisposition(0); + $item->setWeeeTaxDisposition(0); + + $item->setBaseWeeeTaxRowDisposition(0); + $item->setWeeeTaxRowDisposition(0); + + $item->setBaseWeeeTaxAppliedAmount(0); + $item->setBaseWeeeTaxAppliedRowAmount(0); + + $item->setWeeeTaxAppliedAmount(0); + $item->setWeeeTaxAppliedRowAmount(0); + } + + /** + * Fetch FPT data to address object for display in totals block + * + * @param Mage_Sales_Model_Quote_Address $address + * @return Mage_Weee_Model_Total_Quote_Weee + */ + public function fetch(Mage_Sales_Model_Quote_Address $address) + { + return $this; + } + + /** + * Process model configuration array. + * This method can be used for changing totals collect sort order + * + * @param array $config + * @param store $store + * @return array + */ + public function processConfigArray($config, $store) + { + return $config; + } + + /** + * Process item fixed taxes + * + * @deprecated since 1.3.2.3 + * @param Mage_Sales_Model_Quote_Address $address + * @param Mage_Sales_Model_Quote_Item_Abstract $item + * @param bool $updateParent + * @return Mage_Weee_Model_Total_Quote_Weee + */ protected function _processItem(Mage_Sales_Model_Quote_Address $address, $item, $updateParent = false) { - $custTaxClassId = $address->getQuote()->getCustomerTaxClassId(); $store = $address->getQuote()->getStore(); + if (!$this->_helper->isEnabled($store)) { + return $this; + } + $custTaxClassId = $address->getQuote()->getCustomerTaxClassId(); $taxCalculationModel = Mage::getSingleton('tax/calculation'); /* @var $taxCalculationModel Mage_Tax_Model_Calculation */ $request = $taxCalculationModel->getRateRequest($address, $address->getQuote()->getBillingAddress(), $custTaxClassId, $store); $defaultRateRequest = $taxCalculationModel->getRateRequest(false, false, false, $store); - $attributes = Mage::helper('weee')->getProductWeeeAttributes( + $attributes = $this->_helper->getProductWeeeAttributes( $item->getProduct(), $address, $address->getQuote()->getBillingAddress(), @@ -98,30 +348,29 @@ protected function _processItem(Mage_Sales_Model_Quote_Address $address, $item, $title = $attribute->getName(); - if ($item->getDiscountPercent() && Mage::helper('weee')->isDiscounted($store)) { + /** + * Apply discount to fixed tax + */ + if ($item->getDiscountPercent() && $this->_helper->isDiscounted($store)) { $valueDiscount = $value/100*$item->getDiscountPercent(); $baseValueDiscount = $baseValue/100*$item->getDiscountPercent(); $rowValueDiscount = $rowValue/100*$item->getDiscountPercent(); $baseRowValueDiscount = $baseRowValue/100*$item->getDiscountPercent(); - -// $value = $store->roundPrice($value-$valueDiscount); -// $baseValue = $store->roundPrice($baseValue-$baseValueDiscount); -// $rowValue = $store->roundPrice($rowValue-$rowValueDiscount); -// $baseRowValue = $store->roundPrice($baseRowValue-$baseRowValueDiscount); - - $address->setDiscountAmount($address->getDiscountAmount()+$rowValueDiscount); $address->setBaseDiscountAmount($address->getBaseDiscountAmount()+$baseRowValueDiscount); - + $address->setGrandTotal($address->getGrandTotal() - $rowValueDiscount); $address->setBaseGrandTotal($address->getBaseGrandTotal() - $baseRowValueDiscount); } $oneDisposition = $baseOneDisposition = $disposition = $baseDisposition = 0; - if (Mage::helper('weee')->isTaxable($store)) { + /** + * Apply tax percent to fixed tax + */ + if ($this->_helper->isTaxable($store)) { $currentPercent = $item->getTaxPercent(); $defaultPercent = $taxCalculationModel->getRate($defaultRateRequest->setProductClassId($item->getProduct()->getTaxClassId())); @@ -134,13 +383,9 @@ protected function _processItem(Mage_Sales_Model_Quote_Address $address, $item, $disposition = $store->roundPrice($rowValue/(100+$defaultPercent)*$currentPercent); $baseDisposition = $store->roundPrice($baseRowValue/(100+$defaultPercent)*$currentPercent); - //$totalWeeeTax += $disposition; - //$baseTotalWeeeTax += $baseDisposition; - $item->setBaseTaxAmount($item->getBaseTaxAmount()+$baseDisposition); $item->setTaxAmount($item->getTaxAmount()+$disposition); - $value -= $oneDisposition; $baseValue -= $baseOneDisposition; @@ -152,14 +397,14 @@ protected function _processItem(Mage_Sales_Model_Quote_Address $address, $item, $item->setWeeeTaxRowDisposition($item->getWeeeTaxRowDisposition() + $disposition); $item->setBaseWeeeTaxRowDisposition($item->getBaseWeeeTaxRowDisposition() + $baseDisposition); - $item->setTaxBeforeDiscount($item->getTaxBeforeDiscount() + $disposition); - $item->setBaseTaxBeforeDiscount($item->getBaseTaxBeforeDiscount() + $baseDisposition); +// $item->setTaxBeforeDiscount($item->getTaxBeforeDiscount() + $disposition); +// $item->setBaseTaxBeforeDiscount($item->getBaseTaxBeforeDiscount() + $baseDisposition); $address->setTaxAmount($address->getTaxAmount() + $disposition); $address->setBaseTaxAmount($address->getBaseTaxAmount() + $baseDisposition); $rate = $taxCalculationModel->getRate($request->setProductClassId($item->getProduct()->getTaxClassId())); - + $this->_saveAppliedTaxes( $address, $taxCalculationModel->getAppliedRates($request), @@ -172,12 +417,15 @@ protected function _processItem(Mage_Sales_Model_Quote_Address $address, $item, $address->setBaseGrandTotal($address->getBaseGrandTotal() + $store->roundPrice($baseValueBeforeVAT-$baseRowValue)); } - if (Mage::helper('weee')->includeInSubtotal($store)) { + /** + * Check if need include fixed tax amount to subtotal + */ + if ($this->_helper->includeInSubtotal($store)) { $address->setSubtotal($address->getSubtotal() + $rowValue); $address->setBaseSubtotal($address->getBaseSubtotal() + $baseRowValue); - $address->setSubtotalWithDiscount($address->getSubtotalWithDiscount() + $rowValue); - $address->setBaseSubtotalWithDiscount($address->getBaseSubtotalWithDiscount() + $baseRowValue); +// $address->setSubtotalWithDiscount($address->getSubtotalWithDiscount() + $rowValue); +// $address->setBaseSubtotalWithDiscount($address->getBaseSubtotalWithDiscount() + $baseRowValue); } else { $address->setTaxAmount($address->getTaxAmount() + $rowValue); $address->setBaseTaxAmount($address->getBaseTaxAmount() + $baseRowValue); @@ -202,7 +450,7 @@ protected function _processItem(Mage_Sales_Model_Quote_Address $address, $item, $applied[] = array( 'id'=>$attribute->getCode(), 'percent'=>null, - 'hidden'=>Mage::helper('weee')->includeInSubtotal($store), + 'hidden'=>$this->_helper->includeInSubtotal($store), 'rates' => array(array( 'amount'=>$rowValue, 'base_amount'=>$baseRowValue, @@ -222,7 +470,7 @@ protected function _processItem(Mage_Sales_Model_Quote_Address $address, $item, $item->setWeeeTaxAppliedRowAmount($item->getWeeeTaxAppliedRowAmount() + $rowValue); } - Mage::helper('weee')->setApplied($item, array_merge(Mage::helper('weee')->getApplied($item), $productTaxes)); + $this->_helper->setApplied($item, array_merge($this->_helper->getApplied($item), $productTaxes)); if ($updateParent) { $parent = $item->getParentItem(); @@ -250,26 +498,4 @@ protected function _processItem(Mage_Sales_Model_Quote_Address $address, $item, ); } } - - protected function _resetItemData($item) - { - Mage::helper('weee')->setApplied($item, array()); - - $item->setBaseWeeeTaxDisposition(0); - $item->setWeeeTaxDisposition(0); - - $item->setBaseWeeeTaxRowDisposition(0); - $item->setWeeeTaxRowDisposition(0); - - $item->setBaseWeeeTaxAppliedAmount(0); - $item->setBaseWeeeTaxAppliedRowAmount(0); - - $item->setWeeeTaxAppliedAmount(0); - $item->setWeeeTaxAppliedRowAmount(0); - } - - public function fetch(Mage_Sales_Model_Quote_Address $address) - { - return $this; - } } \ No newline at end of file diff --git a/app/code/core/Mage/Weee/etc/config.xml b/app/code/core/Mage/Weee/etc/config.xml index 6003479da8..f33a4ba8b1 100644 --- a/app/code/core/Mage/Weee/etc/config.xml +++ b/app/code/core/Mage/Weee/etc/config.xml @@ -31,58 +31,35 @@ 0.13 - Mage_Weee_Model weee_mysql4 - Mage_Weee_Model_Mysql4 - - weee_tax - - - weee_discount - + weee_tax + weee_discount - Mage_Weee Mage_Weee_Model_Mysql4_Setup - - core_setup - - - - core_write - - - - - core_read - - - Mage_Weee_Block - - singleton weee/observer setWeeeRendererInForm @@ -91,7 +68,6 @@ - singleton weee/observer updateExcludedFieldList @@ -129,7 +105,6 @@ - singleton weee/observer updateElementTypes @@ -139,7 +114,6 @@ - singleton weee/observer updateDiscountPercents @@ -149,7 +123,6 @@ - singleton weee/observer updateCofigurableProductOptions @@ -158,7 +131,6 @@ - singleton weee/observer updateBundleProductOptions @@ -241,8 +213,8 @@ weee/total_quote_weee - subtotal,shipping,tax - grand_total + subtotal,tax_subtotal + tax,discount @@ -291,9 +263,7 @@ - - weee.xml - + weee.xml diff --git a/app/code/core/Mage/Weee/etc/system.xml b/app/code/core/Mage/Weee/etc/system.xml index f9f9f6f6f8..b5afd54822 100644 --- a/app/code/core/Mage/Weee/etc/system.xml +++ b/app/code/core/Mage/Weee/etc/system.xml @@ -82,7 +82,7 @@ 0 - Apply Percent Discounts To FPT + Apply Discounts To FPT select adminhtml/system_config_source_yesno 50 diff --git a/app/code/core/Mage/Wishlist/Helper/Data.php b/app/code/core/Mage/Wishlist/Helper/Data.php index 298815c940..3c9c5d7eab 100644 --- a/app/code/core/Mage/Wishlist/Helper/Data.php +++ b/app/code/core/Mage/Wishlist/Helper/Data.php @@ -172,9 +172,21 @@ public function getRemoveUrl($item) * Retrieve url for adding product to wishlist * * @param Mage_Catalog_Model_Product|Mage_Wishlist_Model_Item $product - * @return string + * @return string|boolean */ public function getAddUrl($item) + { + return $this->getAddUrlWithParams($item); + } + + /** + * Retrieve url for adding product to wishlist with params + * + * @param Mage_Catalog_Model_Product|Mage_Wishlist_Model_Item $product + * @param array $param + * @return string|boolean + */ + public function getAddUrlWithParams($item, array $params = array()) { $productId = null; if ($item instanceof Mage_Catalog_Model_Product) { @@ -185,9 +197,8 @@ public function getAddUrl($item) } if ($productId) { - return $this->_getUrlStore($item)->getUrl('wishlist/index/add', array( - 'product' => $productId - )); + $params['product'] = $productId; + return $this->_getUrlStore($item)->getUrl('wishlist/index/add', $params); } return false; diff --git a/app/code/core/Mage/Wishlist/Model/Mysql4/Product/Collection.php b/app/code/core/Mage/Wishlist/Model/Mysql4/Product/Collection.php index bb6bbdfc40..c046d6f413 100644 --- a/app/code/core/Mage/Wishlist/Model/Mysql4/Product/Collection.php +++ b/app/code/core/Mage/Wishlist/Model/Mysql4/Product/Collection.php @@ -35,6 +35,35 @@ class Mage_Wishlist_Model_Mysql4_Product_Collection extends Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection { + + /** + * Add days in whishlist filter of product collection + * + * @var boolean + */ + protected $_addDaysInWishlist = false; + + /** + * Get add days in whishlist filter of product collection flag + * + * @return boolean + */ + public function getDaysInWishlist() + { + return $this->_addDaysInWishlist; + } + + /** + * Set add days in whishlist filter of product collection flag + * + * @return Mage_Wishlist_Model_Mysql4_Product_Collection + */ + public function setDaysInWishlist($flag) + { + $this->_addDaysInWishlist = (bool) $flag; + return $this; + } + /** * Add wishlist filter to collection * @@ -80,6 +109,17 @@ public function addWishListSortOrder($attribute = 'added_at', $dir = 'desc') return $this; } + /** + * Reset sort order + * + * @return Mage_Wishlist_Model_Mysql4_Product_Collection + */ + public function resetSortOrder() + { + $this->getSelect()->reset(Zend_Db_Select::ORDER); + return $this; + } + /** * Add store data (days in wishlist) * @@ -87,15 +127,18 @@ public function addWishListSortOrder($attribute = 'added_at', $dir = 'desc') */ public function addStoreData() { - if (!isset($this->_joinFields['e_id'])) { + if (!$this->getDaysInWishlist()) { return $this; } - $dayTable = $this->_getAttributeTableAlias('days_in_wishlist'); - $this->joinField('store_name', 'core/store', 'name', 'store_id=store_id'); + $this->setDaysInWishlist(false); + + $dayTable = 't_wi'; //$this->_getAttributeTableAlias('days_in_wishlist'); + + $this->joinField('store_name', 'core/store', 'name', 'store_id=item_store_id'); $this->joinField('days_in_wishlist', 'wishlist/item', - "(TO_DAYS('" . Mage::getSingleton('core/date')->date() . "') - TO_DAYS(DATE_ADD(".$dayTable.".added_at, INTERVAL " .(int) Mage::getSingleton('core/date')->getGmtOffset() . " SECOND)))", + "(TO_DAYS('" . (substr(Mage::getSingleton('core/date')->date(), 0, -2) . '00') . "') - TO_DAYS(DATE_ADD(".$dayTable.".added_at, INTERVAL " .(int) Mage::getSingleton('core/date')->getGmtOffset() . " SECOND)))", 'wishlist_item_id=wishlist_item_id' ); diff --git a/app/code/core/Mage/Wishlist/Model/Mysql4/Wishlist/Collection.php b/app/code/core/Mage/Wishlist/Model/Mysql4/Wishlist/Collection.php index ba8495155c..047a41a117 100644 --- a/app/code/core/Mage/Wishlist/Model/Mysql4/Wishlist/Collection.php +++ b/app/code/core/Mage/Wishlist/Model/Mysql4/Wishlist/Collection.php @@ -32,12 +32,13 @@ * @package Mage_Wishlist * @author Magento Core Team */ -class Mage_Wishlist_Model_Mysql4_Wishlist_Collection extends Mage_Core_Model_Abstract +class Mage_Wishlist_Model_Mysql4_Wishlist_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract { - + /** + * Initialize resource + */ protected function _construct() { $this->_init('wishlist/wishlist'); } - } diff --git a/app/code/core/Mage/Wishlist/Model/Observer.php b/app/code/core/Mage/Wishlist/Model/Observer.php index 11e7fb478d..b47808adab 100644 --- a/app/code/core/Mage/Wishlist/Model/Observer.php +++ b/app/code/core/Mage/Wishlist/Model/Observer.php @@ -80,6 +80,7 @@ public function processCartUpdateBefore($observer) $wishlist->addNewItem($productId); } $wishlist->save(); + Mage::helper('wishlist')->calculate(); } return $this; } diff --git a/app/code/core/Mage/Wishlist/controllers/IndexController.php b/app/code/core/Mage/Wishlist/controllers/IndexController.php index a46ccfb150..6ebc181d69 100644 --- a/app/code/core/Mage/Wishlist/controllers/IndexController.php +++ b/app/code/core/Mage/Wishlist/controllers/IndexController.php @@ -89,6 +89,16 @@ public function indexAction() if ($block = $this->getLayout()->getBlock('customer.wishlist')) { $block->setRefererUrl($this->_getRefererUrl()); } + + $session = Mage::getSingleton('customer/session'); + + /** + * Get referer to avoid referring to the compare popup window + */ + if ($block && $referer = $session->getAddActionReferer(true)) { + $block->setRefererUrl($referer); + } + $this->_initLayoutMessages('customer/session'); $this->_initLayoutMessages('checkout/session'); $this->renderLayout(); @@ -130,6 +140,11 @@ public function addAction() $referer = $this->_getRefererUrl(); } + /** + * Set referer to avoid referring to the compare popup window + */ + $session->setAddActionReferer($referer); + Mage::helper('wishlist')->calculate(); $message = $this->__('%1$s was successfully added to your wishlist. Click here to continue shopping', $product->getName(), $referer); @@ -172,6 +187,11 @@ public function updateAction() ); } } + + if (isset($post['save_and_share'])) { + $this->_redirect('*/*/share'); + return; + } } $this->_redirect('*'); } diff --git a/app/code/core/Mage/Wishlist/etc/adminhtml.xml b/app/code/core/Mage/Wishlist/etc/adminhtml.xml new file mode 100644 index 0000000000..d0c679b475 --- /dev/null +++ b/app/code/core/Mage/Wishlist/etc/adminhtml.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + Wishlist Section + + + + + + + + + + diff --git a/app/code/core/Mage/Wishlist/etc/config.xml b/app/code/core/Mage/Wishlist/etc/config.xml index 316d9c74fe..ffa0ee9f28 100644 --- a/app/code/core/Mage/Wishlist/etc/config.xml +++ b/app/code/core/Mage/Wishlist/etc/config.xml @@ -59,20 +59,7 @@ Mage_Wishlist - - core_setup - - - - core_write - - - - - core_read - - @@ -87,7 +74,6 @@ - singleton wishlist/observer processCartUpdateBefore @@ -96,7 +82,6 @@ - singleton wishlist/observer processAddToCart @@ -134,7 +119,6 @@ - singleton wishlist/observer customerLogin @@ -143,7 +127,6 @@ - singleton wishlist/observer customerLogout @@ -152,25 +135,6 @@ - - - - - - - - - - Wishlist Section - - - - - - - - - diff --git a/app/design/adminhtml/default/default/layout/catalog.xml b/app/design/adminhtml/default/default/layout/catalog.xml index f07de83348..db703154fb 100644 --- a/app/design/adminhtml/default/default/layout/catalog.xml +++ b/app/design/adminhtml/default/default/layout/catalog.xml @@ -62,6 +62,71 @@ + + + + + + + catalog.product.edit.tab.related + getSelectedRelatedProducts + links[related] + products_related + + + + + + + + + + + + + + + + + + + catalog.product.edit.tab.upsell + getSelectedUpsellProducts + links[upsell] + products_upsell + + + + + + + + + + + + + + + + + + + catalog.product.edit.tab.crosssell + getSelectedCrossSellProducts + links[crosssell] + products_crosssell + + + + + + + + + + + @@ -71,13 +136,33 @@ Layout handle for simple products - superadminhtml/catalog_product_edit_tab_super_group - + + + + + + + + catalog.product.edit.tab.super.group + getSelectedGroupedProducts + links[grouped] + products_grouped + + + + + + + + + + + diff --git a/app/design/adminhtml/default/default/layout/cms.xml b/app/design/adminhtml/default/default/layout/cms.xml new file mode 100644 index 0000000000..9cdd464acb --- /dev/null +++ b/app/design/adminhtml/default/default/layout/cms.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + main_sectioncms_page_edit_tab_main + content_sectioncms_page_edit_tab_content + design_sectioncms_page_edit_tab_design + meta_sectioncms_page_edit_tab_meta + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/design/adminhtml/default/default/layout/customer.xml b/app/design/adminhtml/default/default/layout/customer.xml index c6dc6844c1..0e80a1f000 100644 --- a/app/design/adminhtml/default/default/layout/customer.xml +++ b/app/design/adminhtml/default/default/layout/customer.xml @@ -31,6 +31,9 @@ + + + diff --git a/app/design/adminhtml/default/default/layout/googleoptimizer.xml b/app/design/adminhtml/default/default/layout/googleoptimizer.xml index b917096fc9..abf070185c 100644 --- a/app/design/adminhtml/default/default/layout/googleoptimizer.xml +++ b/app/design/adminhtml/default/default/layout/googleoptimizer.xml @@ -29,7 +29,7 @@ @@ -52,4 +52,31 @@ Layout handle for budle products + + + + + + + cms_page_edit_tabs + addTab + + googleoptimizer_section + googleoptimizer/adminhtml_cms_page_edit_tab_googleoptimizer + + + + js + googleoptimizer_js + googleoptimizer/js + + googleoptimizer/js.phtml + + + + + + diff --git a/app/design/adminhtml/default/default/layout/index.xml b/app/design/adminhtml/default/default/layout/index.xml new file mode 100644 index 0000000000..68ebf9318b --- /dev/null +++ b/app/design/adminhtml/default/default/layout/index.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + main_sectionindex/adminhtml_process_edit_tab_main + + + + diff --git a/app/design/adminhtml/default/default/layout/main.xml b/app/design/adminhtml/default/default/layout/main.xml index c41de21af4..48d553ca12 100644 --- a/app/design/adminhtml/default/default/layout/main.xml +++ b/app/design/adminhtml/default/default/layout/main.xml @@ -94,13 +94,18 @@ Default layout, loads most of the pages jsmage/adminhtml/rules.jscan_load_rules_js + jsmage/adminhtml/wysiwyg/tiny_mce/setup.jscan_load_tiny_mce + - + + + + diff --git a/app/design/adminhtml/default/default/layout/promo.xml b/app/design/adminhtml/default/default/layout/promo.xml new file mode 100644 index 0000000000..12094c5e0d --- /dev/null +++ b/app/design/adminhtml/default/default/layout/promo.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + 1 + 1 + + + + + + + main_sectionpromo_catalog_edit_tab_main + conditions_sectionpromo_catalog_edit_tab_conditions + actions_sectionpromo_catalog_edit_tab_actions + + + + + + + + + + + + + + + + 1 + 1 + + + + + + + + main_sectionpromo_quote_edit_tab_main + conditions_sectionpromo_quote_edit_tab_conditions + actions_sectionpromo_quote_edit_tab_actions + labels_sectionpromo_quote_edit_tab_labels + + + + + + + + + + + \ No newline at end of file diff --git a/app/design/adminhtml/default/default/layout/sales.xml b/app/design/adminhtml/default/default/layout/sales.xml index 82fe1921ec..a472352e6d 100644 --- a/app/design/adminhtml/default/default/layout/sales.xml +++ b/app/design/adminhtml/default/default/layout/sales.xml @@ -426,6 +426,7 @@ mage/adminhtml/sales.js mage/adminhtml/giftmessage.js + @@ -480,7 +481,7 @@ - + diff --git a/app/design/adminhtml/default/default/layout/tag.xml b/app/design/adminhtml/default/default/layout/tag.xml new file mode 100644 index 0000000000..38a5569e0c --- /dev/null +++ b/app/design/adminhtml/default/default/layout/tag.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + tag_assigned_grid + getRelatedProducts + tag_assigned_products + assigned_products + + + + + + + + + \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/catalog/category/widget/tree.phtml b/app/design/adminhtml/default/default/template/catalog/category/widget/tree.phtml new file mode 100644 index 0000000000..69f0946885 --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/category/widget/tree.phtml @@ -0,0 +1,191 @@ + + +getId() ?> + + + diff --git a/app/design/adminhtml/default/default/template/catalog/product/attribute/set/main.phtml b/app/design/adminhtml/default/default/template/catalog/product/attribute/set/main.phtml index 670e6ecb64..ac7c7b8e12 100644 --- a/app/design/adminhtml/default/default/template/catalog/product/attribute/set/main.phtml +++ b/app/design/adminhtml/default/default/template/catalog/product/attribute/set/main.phtml @@ -161,7 +161,7 @@ for( i in rootNode.childNodes ) { if(rootNode.childNodes[i].id) { var group = rootNode.childNodes[i]; - editSet.req.groups[gIterator] = new Array(group.id, group.attributes.text, (gIterator+1)); + editSet.req.groups[gIterator] = new Array(group.id, group.attributes.text.strip(), (gIterator+1)); var iterator = 0 for( j in group.childNodes ) { iterator ++; @@ -284,7 +284,7 @@ addGroup : function() { var group_name = prompt("__('Please enter a new group name') ?>",""); - + group_name = group_name.strip(); if( group_name == '' ) { this.addGroup(); } else if( group_name != false && group_name != null && group_name != '' ) { @@ -300,6 +300,9 @@ allowDrag : true }); TreePanels.root.appendChild(newNode); + newNode.addListener('beforemove', editSet.groupBeforeMove); + newNode.addListener('beforeinsert', editSet.groupBeforeInsert); + newNode.addListener('beforeappend', editSet.groupBeforeInsert); newNode.addListener('click', editSet.register); } }, @@ -316,6 +319,10 @@ }, validateGroupName : function(name, exceptNodeId) { + name = name.strip(); + if (name === '') { + return false; + } for (var i=0; i < TreePanels.root.childNodes.length; i++) { if (TreePanels.root.childNodes[i].text.toLowerCase() == name.toLowerCase() && TreePanels.root.childNodes[i].id != exceptNodeId) { errorText = "__('Attribute group with the \"/name/\" name already exists') ?>"; diff --git a/app/design/adminhtml/default/default/template/catalog/product/edit.phtml b/app/design/adminhtml/default/default/template/catalog/product/edit.phtml index 96d2b7dcdf..915b511112 100644 --- a/app/design/adminhtml/default/default/template/catalog/product/edit.phtml +++ b/app/design/adminhtml/default/default/template/catalog/product/edit.phtml @@ -112,93 +112,6 @@ return 1; } -getProductSetId() && $this->getIsConfigured()): ?> - var productLinksController = Class.create(); - - productLinksController.prototype = { - initialize : function(fieldId, products, grid) { - this.saveField = $(fieldId); - this.saveFieldId = fieldId; - this.products = $H(products); - this.grid = grid; - this.tabIndex = 1000; - this.grid.rowClickCallback = this.rowClick.bind(this); - this.grid.initRowCallback = this.rowInit.bind(this); - this.grid.checkboxCheckCallback = this.registerProduct.bind(this); - this.grid.rows.each(this.eachRow.bind(this)); - this.saveField.value = this.serializeObject(this.products); - this.grid.reloadParams = {'products[]':this.products.keys()}; - }, - eachRow : function(row) { - this.rowInit(this.grid, row); - }, - registerProduct : function(grid, element, checked) { - if(checked){ - if(element.inputElements) { - this.products.set(element.value, {}); - for(var i = 0; i < element.inputElements.length; i++) { - element.inputElements[i].disabled = false; - this.products.get(element.value)[element.inputElements[i].name] = element.inputElements[i].value; - } - } - } - else{ - if(element.inputElements){ - for(var i = 0; i < element.inputElements.length; i++) { - element.inputElements[i].disabled = true; - } - } - - this.products.unset(element.value); - } - this.saveField.value = this.serializeObject(this.products); - this.grid.reloadParams = {'products[]':this.products.keys()}; - }, - serializeObject : function(hash) { - var clone = hash.clone(); - clone.each(function(pair) { - clone.set(pair.key, encode_base64(Object.toQueryString(pair.value))); - }); - return clone.toQueryString(); - }, - rowClick : function(grid, event) { - var trElement = Event.findElement(event, 'tr'); - var isInput = Event.element(event).tagName == 'INPUT'; - if(trElement){ - var checkbox = Element.select(trElement, 'input'); - if(checkbox[0]){ - var checked = isInput ? checkbox[0].checked : !checkbox[0].checked; - this.grid.setCheckboxChecked(checkbox[0], checked); - } - } - }, - inputChange : function(event) { - var element = Event.element(event); - if(element && element.checkboxElement && element.checkboxElement.checked){ - this.products.get(element.checkboxElement.value)[element.name] = element.value; - this.saveField.value = this.serializeObject(this.products); - } - }, - rowInit : function(grid, row) { - var checkbox = $(row).select('.checkbox')[0]; - var inputs = $(row).select('.input-text'); - if(checkbox && inputs.length > 0) { - checkbox.inputElements = inputs; - for(var i = 0; i < inputs.length; i++) { - inputs[i].checkboxElement = checkbox; - if(this.products.get(checkbox.value) && this.products.get(checkbox.value)[inputs[i].name]) { - inputs[i].value = this.products.get(checkbox.value)[inputs[i].name]; - } - inputs[i].disabled = !checkbox.checked; - inputs[i].tabIndex = this.tabIndex++; - Event.observe(inputs[i],'keyup', this.inputChange.bind(this)); - Event.observe(inputs[i],'change', this.inputChange.bind(this)); - } - } - } - }; - - Event.observe(window, 'load', function() { var objName = 'getSelectedTabId() ?>'; if (objName) { diff --git a/app/design/adminhtml/default/default/template/catalog/product/edit/action/websites.phtml b/app/design/adminhtml/default/default/template/catalog/product/edit/action/websites.phtml index cb1b2ff0c2..1e0d39f17b 100644 --- a/app/design/adminhtml/default/default/template/catalog/product/edit/action/websites.phtml +++ b/app/design/adminhtml/default/default/template/catalog/product/edit/action/websites.phtml @@ -32,7 +32,7 @@ getWebsiteCollection() as $_website): ?> - + getWebsitesReadonly()): ?>disabled="disabled" class="checkbox website-checkbox" id="add_product_website_getId() ?>" type="checkbox" /> getName() ?> @@ -70,7 +70,7 @@ getWebsiteCollection() as $_website): ?> - + getWebsitesReadonly()): ?>disabled="disabled" class="checkbox website-checkbox" id="remove_product_website_getId() ?>" type="checkbox"/> getName() ?> diff --git a/app/design/adminhtml/default/default/template/catalog/product/edit/price/tier.phtml b/app/design/adminhtml/default/default/template/catalog/product/edit/price/tier.phtml index 9d9323aed2..56f36bc534 100644 --- a/app/design/adminhtml/default/default/template/catalog/product/edit/price/tier.phtml +++ b/app/design/adminhtml/default/default/template/catalog/product/edit/price/tier.phtml @@ -23,17 +23,23 @@ * @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) */ +/* @var $this Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Price_Tier */ ?> getElement()->getHtmlId() ?> getElement()->getClass() ?> getElement()->getName() ?> getElement()->getReadonly() ?> -isMultiWebsites(); ?> +isShowWebsiteColumn(); ?> +isAllowChangeWebsite(); ?> + + +isShowWebsiteColumn(); ?> +isMultiWebsites(); ?> getElement()->getLabel() ?> - + @@ -42,102 +48,120 @@ - style="display:none">__('Website') ?> + style="display:none">__('Website') ?> __('Customer Group') ?> __('Qty') ?> getPriceColumnHeader(Mage::helper('catalog')->__('Price')) ?> __('Action') ?> - - style="display:none"> - - getWebsites() as $_websiteId => $_info): ?> - [] - - - - - - getCustomerGroups() as $_groupId=>$_groupName): ?> - - - - - - __('and above')?> - - - - - Delete - + - style="display:none"> + style="display:none"> getAddButtonHtml() ?> - - - - + diff --git a/app/design/adminhtml/default/default/template/catalog/product/edit/serializer.phtml b/app/design/adminhtml/default/default/template/catalog/product/edit/serializer.phtml index ea96d0820c..7768569035 100644 --- a/app/design/adminhtml/default/default/template/catalog/product/edit/serializer.phtml +++ b/app/design/adminhtml/default/default/template/catalog/product/edit/serializer.phtml @@ -1,3 +1,30 @@ + + +getChildHtml('wysiwyg_images.uploader') ?> + +getChildHtml('wysiwyg_images.newfolder') ?> diff --git a/app/design/adminhtml/default/default/template/cms/wysiwyg/content/files.phtml b/app/design/adminhtml/default/default/template/cms/wysiwyg/content/files.phtml new file mode 100644 index 0000000000..805db56915 --- /dev/null +++ b/app/design/adminhtml/default/default/template/cms/wysiwyg/content/files.phtml @@ -0,0 +1,48 @@ + + +getImagesWidth(); +$_height = $this->getImagesHeight(); +?> +getContentsCollection()->count() > 0): ?> + getContentsCollection() as $item): ?> + + + getWidth(); ?>xgetHeight(); ?> helper('cms')->__('px.') ?> + getName(); ?> + + + + helper('cms')->__('No files found') ?> + diff --git a/app/design/adminhtml/default/default/template/cms/wysiwyg/content/newfolder.phtml b/app/design/adminhtml/default/default/template/cms/wysiwyg/content/newfolder.phtml new file mode 100644 index 0000000000..1ed23712b5 --- /dev/null +++ b/app/design/adminhtml/default/default/template/cms/wysiwyg/content/newfolder.phtml @@ -0,0 +1,33 @@ + + diff --git a/app/design/adminhtml/default/default/template/cms/wysiwyg/content/uploader.phtml b/app/design/adminhtml/default/default/template/cms/wysiwyg/content/uploader.phtml new file mode 100644 index 0000000000..2873e1b4c7 --- /dev/null +++ b/app/design/adminhtml/default/default/template/cms/wysiwyg/content/uploader.phtml @@ -0,0 +1,71 @@ + + +helper('adminhtml/media_js')->includeScript('lib/flex.js') ?> +helper('adminhtml/media_js')->includeScript('mage/adminhtml/flexuploader.js') ?> +helper('adminhtml/media_js')->getTranslatorScript() ?> + + + + + + __('This content requires last version of Adobe Flash Player. Get Flash', 'http://www.adobe.com/go/getflash/') ?> + + + + + + {{name}} ({{size}}) + getDeleteButtonHtml() ?> + + + + + + {{percent}}% {{uploaded}} / {{total}} + + + + \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/cms/wysiwyg/js.phtml b/app/design/adminhtml/default/default/template/cms/wysiwyg/js.phtml new file mode 100644 index 0000000000..95e9faf255 --- /dev/null +++ b/app/design/adminhtml/default/default/template/cms/wysiwyg/js.phtml @@ -0,0 +1,262 @@ + + + diff --git a/app/design/adminhtml/default/default/template/cms/wysiwyg/tree.phtml b/app/design/adminhtml/default/default/template/cms/wysiwyg/tree.phtml new file mode 100644 index 0000000000..5b2a356c63 --- /dev/null +++ b/app/design/adminhtml/default/default/template/cms/wysiwyg/tree.phtml @@ -0,0 +1,76 @@ + + + + + helper('cms')->__('Collapse All'); ?> + | + helper('cms')->__('Expand All'); ?> + + + + + + diff --git a/app/design/adminhtml/default/default/template/customer/tab/addresses.phtml b/app/design/adminhtml/default/default/template/customer/tab/addresses.phtml index 875075a588..fa8ade5cb3 100644 --- a/app/design/adminhtml/default/default/template/customer/tab/addresses.phtml +++ b/app/design/adminhtml/default/default/template/customer/tab/addresses.phtml @@ -145,7 +145,6 @@ addressesModel.prototype = { this.loader = new varienLoader(true); this.regionsUrl = 'getRegionsUrl() ?>'; - this.zipOptionalCountries = getCountriesWithOptionalZipJson() ?>; this.reloadItemList(1); @@ -188,7 +187,7 @@ addressesModel.prototype = { // set Zip optional/required var countryElement = $('id' + item.id.replace(/address_item_/, '') + 'country_id'); - this.setZipOptional(countryElement); + varienGlobalEvents.fireEvent("address_country_changed", countryElement); } }, @@ -430,7 +429,13 @@ addressesModel.prototype = { bindCountryRegionRelation : function(parentId){ //alert('OK'); - var countryElements = $$('.countries'); + if(parentId){ + var countryElements = $(parentId).getElementsByClassName('countries'); + } + else{ + var countryElements = $$('.countries'); + } + for(var i=0;i<=countryElements.length;i++){ if(countryElements[i]){ if(!countryElements[i].bindRegions || !countryElements[i].parentBindId || countryElements[i].parentBindId!=parentId){ @@ -454,18 +459,7 @@ addressesModel.prototype = { this.loader.load(url, {}, this.refreshRegionField.bind(this)); } // set Zip optional/required - this.setZipOptional(countryElement); - } - }, - - setZipOptional : function (countryElement){ - var zipElement = $(countryElement.id.replace(/country_id/, 'postcode')) - if (zipElement && this.zipOptionalCountries.indexOf(countryElement.value) != -1) { - zipElement.removeClassName('required-entry').removeClassName('required-entry'); - zipElement.up(1).down('label > span.required').hide(); - } else { - zipElement.addClassName('required-entry'); - zipElement.up(1).down('label > span.required').show(); + varienGlobalEvents.fireEvent("address_country_changed", countryElement); } }, diff --git a/app/design/adminhtml/default/default/template/cybersource/pdf/info.phtml b/app/design/adminhtml/default/default/template/cybersource/pdf/info.phtml index 44edabee01..c063d3ae8b 100644 --- a/app/design/adminhtml/default/default/template/cybersource/pdf/info.phtml +++ b/app/design/adminhtml/default/default/template/cybersource/pdf/info.phtml @@ -33,8 +33,6 @@ {{pdf_row_separator}} __('Credit Card Number: xxxx-%s', $this->getInfo()->getCcLast4()) ?> {{pdf_row_separator}} - __('Expiration Date: %s/%s', $this->getCcExpMonth(), $this->getInfo()->getCcExpYear()) ?> - {{pdf_row_separator}} getInfo()->getCcSsIssue()): ?> __("Switch/Solo card issue number: %s", $this->getInfo()->getCcSsIssue()) ?> {{pdf_row_separator}} diff --git a/app/design/adminhtml/default/default/template/dashboard/index.phtml b/app/design/adminhtml/default/default/template/dashboard/index.phtml index 554da142e6..3658a7d3dd 100644 --- a/app/design/adminhtml/default/default/template/dashboard/index.phtml +++ b/app/design/adminhtml/default/default/template/dashboard/index.phtml @@ -55,6 +55,29 @@ function changeDiagramsPeriod(periodObj) { } }); + ajaxBlockUrl = 'getUrl('*/*/ajaxBlock', array('_current' => true, 'block' => 'totals', 'period' => '')) ?>' + periodParam; + new Ajax.Request(ajaxBlockUrl, { + parameters: {isAjax: 'true', form_key: FORM_KEY}, + onSuccess: function(transport) { + tabContentElementId = 'dashboard_diagram_totals'; + try { + if (transport.responseText.isJSON()) { + var response = transport.responseText.evalJSON() + if (response.error) { + alert(response.message); + } + if(response.ajaxExpired && response.ajaxRedirect) { + setLocation(response.ajaxRedirect); + } + } else { + $(tabContentElementId).replace(transport.responseText); + } + } + catch (e) { + $(tabContentElementId).replace(transport.responseText); + } + } + }); } function toggleCal(id) { diff --git a/app/design/adminhtml/default/default/template/dashboard/totalbar.phtml b/app/design/adminhtml/default/default/template/dashboard/totalbar.phtml index 536cc6bddf..c9832a39f3 100644 --- a/app/design/adminhtml/default/default/template/dashboard/totalbar.phtml +++ b/app/design/adminhtml/default/default/template/dashboard/totalbar.phtml @@ -25,7 +25,7 @@ */ ?> getTotals()) > 0 ): ?> - + diff --git a/app/design/adminhtml/default/default/template/directory/js/optional_zip_countries.phtml b/app/design/adminhtml/default/default/template/directory/js/optional_zip_countries.phtml new file mode 100644 index 0000000000..9c901a482b --- /dev/null +++ b/app/design/adminhtml/default/default/template/directory/js/optional_zip_countries.phtml @@ -0,0 +1,66 @@ + + + + diff --git a/app/design/adminhtml/default/default/template/eav/attribute/edit/js.phtml b/app/design/adminhtml/default/default/template/eav/attribute/edit/js.phtml new file mode 100644 index 0000000000..126d85e88d --- /dev/null +++ b/app/design/adminhtml/default/default/template/eav/attribute/edit/js.phtml @@ -0,0 +1,26 @@ + diff --git a/app/design/adminhtml/default/default/template/eway/pdf/info.phtml b/app/design/adminhtml/default/default/template/eway/pdf/info.phtml index f7e0ef20fd..d367451b3a 100644 --- a/app/design/adminhtml/default/default/template/eway/pdf/info.phtml +++ b/app/design/adminhtml/default/default/template/eway/pdf/info.phtml @@ -29,5 +29,3 @@ __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> {{pdf_row_separator}} __('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> - {{pdf_row_separator}} -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> diff --git a/app/design/adminhtml/default/default/template/flo2cash/pdf/info.phtml b/app/design/adminhtml/default/default/template/flo2cash/pdf/info.phtml index fa6327033a..bcb10763ab 100644 --- a/app/design/adminhtml/default/default/template/flo2cash/pdf/info.phtml +++ b/app/design/adminhtml/default/default/template/flo2cash/pdf/info.phtml @@ -31,5 +31,3 @@ __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> {{pdf_row_separator}} __('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> - {{pdf_row_separator}} -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> diff --git a/app/design/adminhtml/default/default/template/index/notifications.phtml b/app/design/adminhtml/default/default/template/index/notifications.phtml new file mode 100644 index 0000000000..c17530a38b --- /dev/null +++ b/app/design/adminhtml/default/default/template/index/notifications.phtml @@ -0,0 +1,34 @@ + +getProcessesForReindex()?> + + + helper('index')->__('One or more of the Indexes are not up to date:') ?> + . + helper('index')->__('Click here to go to Index Management and rebuild required indexes.', $this->getManageUrl());?> + + \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/media/uploader.phtml b/app/design/adminhtml/default/default/template/media/uploader.phtml index 2d0aa88180..eba74548ee 100644 --- a/app/design/adminhtml/default/default/template/media/uploader.phtml +++ b/app/design/adminhtml/default/default/template/media/uploader.phtml @@ -24,6 +24,12 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> + + helper('adminhtml/media_js')->includeScript('lib/flex.js') ?> helper('adminhtml/media_js')->includeScript('mage/adminhtml/flexuploader.js') ?> helper('adminhtml/media_js')->getTranslatorScript() ?> @@ -32,8 +38,8 @@ - getBrowseButtonHtml() */ ?> - getUploadButtonHtml() */ ?> + getBrowseButtonHtml()*/ ?> + getUploadButtonHtml()*/ ?> __('This content requires last version of Adobe Flash Player. Get Flash', 'http://www.adobe.com/go/getflash/') ?> @@ -51,13 +57,14 @@ {{percent}}% {{uploaded}} / {{total}} + +getCanLoadTinyMce()): // TinyMCE is broken when loaded through index.php ?> + + + helper('core/js')->getTranslatorScript() ?> -getChildHtml('calendar'); \ No newline at end of file +getChildHtml('calendar'); ?> +getChildHtml('optional_zip_countries'); ?> \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/paybox/direct/pdf/info.phtml b/app/design/adminhtml/default/default/template/paybox/direct/pdf/info.phtml index 682b848c2f..215f4fdf6d 100644 --- a/app/design/adminhtml/default/default/template/paybox/direct/pdf/info.phtml +++ b/app/design/adminhtml/default/default/template/paybox/direct/pdf/info.phtml @@ -31,5 +31,3 @@ __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> {{pdf_row_separator}} __('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> - {{pdf_row_separator}} -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> diff --git a/app/design/adminhtml/default/default/template/payment/info/pdf/cc.phtml b/app/design/adminhtml/default/default/template/payment/info/pdf/cc.phtml index d6a41cf005..dedb7b548d 100644 --- a/app/design/adminhtml/default/default/template/payment/info/pdf/cc.phtml +++ b/app/design/adminhtml/default/default/template/payment/info/pdf/cc.phtml @@ -28,6 +28,4 @@ {{pdf_row_separator}} __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> {{pdf_row_separator}} -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> - {{pdf_row_separator}} -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/payment/info/pdf/ccsave.phtml b/app/design/adminhtml/default/default/template/payment/info/pdf/ccsave.phtml index d6c761a528..dedb7b548d 100644 --- a/app/design/adminhtml/default/default/template/payment/info/pdf/ccsave.phtml +++ b/app/design/adminhtml/default/default/template/payment/info/pdf/ccsave.phtml @@ -28,6 +28,4 @@ {{pdf_row_separator}} __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> {{pdf_row_separator}} -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> - {{pdf_row_separator}} -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> \ No newline at end of file +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/paypaluk/direct/pdf/info.phtml b/app/design/adminhtml/default/default/template/paypaluk/direct/pdf/info.phtml index 0bd5c8b780..38041d6808 100644 --- a/app/design/adminhtml/default/default/template/paypaluk/direct/pdf/info.phtml +++ b/app/design/adminhtml/default/default/template/paypaluk/direct/pdf/info.phtml @@ -31,8 +31,6 @@ {{pdf_row_separator}} __('Credit Card Number: xxxx-%s', $this->getInfo()->getCcLast4()) ?> {{pdf_row_separator}} - __('Expiration Date: %s/%s', $this->getCcExpMonth(), $this->getInfo()->getCcExpYear()) ?> - {{pdf_row_separator}} getInfo()->getCcSsIssue()): ?> __("Switch/Solo card issue number: %s", $this->getInfo()->getCcSsIssue()) ?> {{pdf_row_separator}} diff --git a/app/design/adminhtml/default/default/template/popup.phtml b/app/design/adminhtml/default/default/template/popup.phtml index 962c23d17f..5ab2a1069c 100644 --- a/app/design/adminhtml/default/default/template/popup.phtml +++ b/app/design/adminhtml/default/default/template/popup.phtml @@ -42,7 +42,7 @@ getChildHtml('head') ?> - +getBodyClass()?' class="'.$this->getBodyClass().'"':'' ?>> @@ -64,9 +64,11 @@ + getChildHtml('footer')): ?> + getChildHtml('js') ?> getChildHtml('profiler') ?> diff --git a/app/design/adminhtml/default/default/template/report/grid.phtml b/app/design/adminhtml/default/default/template/report/grid.phtml index 0148f2cea9..fe59fd6903 100644 --- a/app/design/adminhtml/default/default/template/report/grid.phtml +++ b/app/design/adminhtml/default/default/template/report/grid.phtml @@ -221,7 +221,8 @@ $numColumns = sizeof($this->getColumns()); } var formParam = new Array('period_date_from', 'period_date_to', 'report_period'); var paramURL = ''; - var switchURL = 'getGridUrl(); ?>'.replace(/(store|group|website)\/\d+\//,''); + var switchURL = 'getAbsoluteGridUrl(array('_current' => false)); ?>'.replace(/(store|group|website)\/\d+\//,''); + for(var i=0;i + isShow()): ?> __('Show Report for') ?>: @@ -67,4 +72,4 @@ setLocation('getSwitchUrl() ?>'+storeParam); } - \ No newline at end of file + diff --git a/app/design/adminhtml/default/default/template/sales/order/create/coupons/form.phtml b/app/design/adminhtml/default/default/template/sales/order/create/coupons/form.phtml index 726317de6a..1236382b13 100644 --- a/app/design/adminhtml/default/default/template/sales/order/create/coupons/form.phtml +++ b/app/design/adminhtml/default/default/template/sales/order/create/coupons/form.phtml @@ -24,6 +24,12 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> + __('Apply Coupon Code') ?> diff --git a/app/design/adminhtml/default/default/template/sales/order/view/info.phtml b/app/design/adminhtml/default/default/template/sales/order/view/info.phtml index 41fb97a639..95d66a66d0 100644 --- a/app/design/adminhtml/default/default/template/sales/order/view/info.phtml +++ b/app/design/adminhtml/default/default/template/sales/order/view/info.phtml @@ -84,7 +84,7 @@ $orderStoreDate = $this->formatDate($_order->getCreatedAtStoreDate(), 'medium', getRemoteIp()): ?> __('Placed from IP') ?> - getRemoteIp() ?> + getRemoteIp(); echo ($_order->getXForwardedFor())?' (' . $_order->getXForwardedFor() . ')':''; ?> getGlobalCurrencyCode() != $_order->getBaseCurrencyCode()): ?> diff --git a/app/design/adminhtml/default/default/template/system/cache/edit.phtml b/app/design/adminhtml/default/default/template/system/cache/edit.phtml index abae89aabc..d4c98b30a1 100644 --- a/app/design/adminhtml/default/default/template/system/cache/edit.phtml +++ b/app/design/adminhtml/default/default/template/system/cache/edit.phtml @@ -59,6 +59,8 @@ getCatalogData() as $_item): ?> + + diff --git a/app/design/adminhtml/default/default/template/system/convert/profile/wizard.phtml b/app/design/adminhtml/default/default/template/system/convert/profile/wizard.phtml index a18ed05c60..5a3a2691e3 100644 --- a/app/design/adminhtml/default/default/template/system/convert/profile/wizard.phtml +++ b/app/design/adminhtml/default/default/template/system/convert/profile/wizard.phtml @@ -143,10 +143,12 @@ function changeDirection() function updateRun(select) { - if ($(select).value=='interactive') { - $('file_list').show(); - } else { - $('file_list').hide(); + if ($('file_list') != null){ + if ($(select).value=='interactive') { + $('file_list').show(); + } else { + $('file_list').hide(); + } } } diff --git a/app/design/adminhtml/default/default/template/system/email/template/edit.phtml b/app/design/adminhtml/default/default/template/system/email/template/edit.phtml index f93b7825ba..a4aa38797c 100644 --- a/app/design/adminhtml/default/default/template/system/email/template/edit.phtml +++ b/app/design/adminhtml/default/default/template/system/email/template/edit.phtml @@ -156,6 +156,8 @@ preview: function() { if (this.typeChange) { $('preview_type').value = 1; + } else { + $('preview_type').value = 2; } if (typeof tinyMCE == 'undefined' || !tinyMCE.getInstanceById('template_text')) { $('preview_text').value = $('template_text').value; diff --git a/app/design/adminhtml/default/default/template/tag/edit/container.phtml b/app/design/adminhtml/default/default/template/tag/edit/container.phtml new file mode 100644 index 0000000000..74df22d158 --- /dev/null +++ b/app/design/adminhtml/default/default/template/tag/edit/container.phtml @@ -0,0 +1,59 @@ + +getFormInitScripts() ?> + + getHeaderHtml() ?> + getButtonsHtml('header') ?> + +isSingleStoreMode() ): ?> + getStoreSwitcherHtml() ?> + + + + getFormHtml() ?> + + getTagAssignAccordionHtml() ?> + + + + + +hasFooterButtons()): ?> + + + +getFormScripts() ?> +getAcordionsHtml() ?> \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/tag/edit/serializer.phtml b/app/design/adminhtml/default/default/template/tag/edit/serializer.phtml new file mode 100644 index 0000000000..7768569035 --- /dev/null +++ b/app/design/adminhtml/default/default/template/tag/edit/serializer.phtml @@ -0,0 +1,33 @@ + + + + + diff --git a/app/design/adminhtml/default/default/template/tag/index.phtml b/app/design/adminhtml/default/default/template/tag/index.phtml index 5b5b7750c2..19d5510d75 100644 --- a/app/design/adminhtml/default/default/template/tag/index.phtml +++ b/app/design/adminhtml/default/default/template/tag/index.phtml @@ -26,10 +26,10 @@ ?> - - getHeaderHtml(); ?> - getCreateButtonHtml(); ?> - + + getHeaderHtml(); ?> + getCreateButtonHtml(); ?> + diff --git a/app/design/adminhtml/default/default/template/widget/form/renderer/fieldset/element.phtml b/app/design/adminhtml/default/default/template/widget/form/renderer/fieldset/element.phtml index 6acc5ca48b..d211a6cc56 100644 --- a/app/design/adminhtml/default/default/template/widget/form/renderer/fieldset/element.phtml +++ b/app/design/adminhtml/default/default/template/widget/form/renderer/fieldset/element.phtml @@ -26,7 +26,8 @@ ?> getElement() ?> getNoDisplay()): ?> - +getHtmlContainerId() ?> + id=""> getType()=='hidden'): ?> getElementHtml()) ?> diff --git a/app/design/adminhtml/default/default/template/widget/grid.phtml b/app/design/adminhtml/default/default/template/widget/grid.phtml index c4f63e445c..0b6642c4c1 100644 --- a/app/design/adminhtml/default/default/template/widget/grid.phtml +++ b/app/design/adminhtml/default/default/template/widget/grid.phtml @@ -183,7 +183,7 @@ $numColumns = sizeof($this->getColumns()); getRowInitCallback()): ?> getJsObjectName() ?>.initRowCallback = getRowInitCallback() ?>; - getJsObjectName() ?>.rows.each(function(row){getRowInitCallback() ?>(getJsObjectName() ?>, row)}); + getJsObjectName() ?>.initGridRows(); getMassactionBlock()->isAvailable()): ?> getMassactionBlock()->getJavaScript() ?> diff --git a/app/design/adminhtml/default/default/template/widget/grid/massaction.phtml b/app/design/adminhtml/default/default/template/widget/grid/massaction.phtml index 99cd9aafaa..58c4b61be2 100644 --- a/app/design/adminhtml/default/default/template/widget/grid/massaction.phtml +++ b/app/design/adminhtml/default/default/template/widget/grid/massaction.phtml @@ -45,7 +45,7 @@ __('Actions') ?> - + getItems() as $_item): ?> getSelected() ? ' selected="selected"' : '')?>>getLabel() ?> @@ -77,4 +77,4 @@ getJsObjectName() ?>.setGridIds('getGridIdsJson() ?>'); - \ No newline at end of file + diff --git a/app/design/adminhtml/default/default/template/widget/grid/serializer.phtml b/app/design/adminhtml/default/default/template/widget/grid/serializer.phtml new file mode 100644 index 0000000000..39e69b60ec --- /dev/null +++ b/app/design/adminhtml/default/default/template/widget/grid/serializer.phtml @@ -0,0 +1,37 @@ + + + + + diff --git a/app/design/frontend/default/blank/layout/checkout.xml b/app/design/frontend/default/blank/layout/checkout.xml index e2075b77bb..f6be52be0a 100644 --- a/app/design/frontend/default/blank/layout/checkout.xml +++ b/app/design/frontend/default/blank/layout/checkout.xml @@ -48,6 +48,9 @@ Default layout, loads most of the pages configurablecheckout/cart_item_renderer_configurablecheckout/cart/sidebar/default.phtml + + + diff --git a/app/design/frontend/default/blank/layout/customer.xml b/app/design/frontend/default/blank/layout/customer.xml index 3d2db79f1b..8d69db5de8 100644 --- a/app/design/frontend/default/blank/layout/customer.xml +++ b/app/design/frontend/default/blank/layout/customer.xml @@ -119,6 +119,9 @@ New customer registration + + + page/1column.phtml @@ -192,7 +195,7 @@ Customer account pages, rendered for all tabs in dashboard - + @@ -239,6 +242,9 @@ Customer account address edit page + + + diff --git a/app/design/frontend/default/blank/layout/ogone.xml b/app/design/frontend/default/blank/layout/ogone.xml new file mode 100644 index 0000000000..3b4785b540 --- /dev/null +++ b/app/design/frontend/default/blank/layout/ogone.xml @@ -0,0 +1,47 @@ + + + + + + + + page/1column.phtml + + + + + + + + + + + + + + diff --git a/app/design/frontend/default/blank/template/amazonpayments/cba/success.phtml b/app/design/frontend/default/blank/template/amazonpayments/cba/success.phtml index 3aa71b7c3c..76ad44087c 100644 --- a/app/design/frontend/default/blank/template/amazonpayments/cba/success.phtml +++ b/app/design/frontend/default/blank/template/amazonpayments/cba/success.phtml @@ -31,5 +31,5 @@ SUCCESS __('Thank you for your purchase!') ?> __('You will receive an order confirmation email with details of your order and a link to track its progress.') ?> - __('Continue Shopping') ?> + __('Continue Shopping') ?> \ No newline at end of file diff --git a/app/design/frontend/default/blank/template/callouts/right_col.phtml b/app/design/frontend/default/blank/template/callouts/right_col.phtml index 6b53da831f..d5cd639eeb 100644 --- a/app/design/frontend/default/blank/template/callouts/right_col.phtml +++ b/app/design/frontend/default/blank/template/callouts/right_col.phtml @@ -26,6 +26,6 @@ ?> - + diff --git a/app/design/frontend/default/blank/template/catalog/navigation/top.phtml b/app/design/frontend/default/blank/template/catalog/navigation/top.phtml index c22deabf66..f7287bc29d 100644 --- a/app/design/frontend/default/blank/template/catalog/navigation/top.phtml +++ b/app/design/frontend/default/blank/template/catalog/navigation/top.phtml @@ -24,17 +24,17 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> - -getStoreCategories())>1): ?> +getStoreCategories()) > 0): ?> getStoreCategories() as $_category): ?> drawItem($_category) ?> - + diff --git a/app/design/frontend/default/blank/template/catalog/product/compare/list.phtml b/app/design/frontend/default/blank/template/catalog/product/compare/list.phtml index abae5f576f..61d3ac8d71 100644 --- a/app/design/frontend/default/blank/template/catalog/product/compare/list.phtml +++ b/app/design/frontend/default/blank/template/catalog/product/compare/list.phtml @@ -65,7 +65,7 @@ getReviewsSummaryHtml($_item, 'short') ?> getPriceHtml($_item, true) ?> isSaleable()): ?> - __('Add to Cart') ?> + __('Add to Cart') ?> __('Out of stock') ?> @@ -82,7 +82,7 @@ getItems() as $_item): ?> - __($_attribute->getFrontendLabel()) ?> + getStoreLabel() ?> getAttributeCode()) { @@ -111,7 +111,7 @@ getPriceHtml($_item) ?> isSaleable()): ?> - __('Add to Cart') ?> + __('Add to Cart') ?> __('Out of stock') ?> @@ -124,7 +124,7 @@ - __('Close Window') ?> + __('Close Window') ?> diff --git a/app/design/frontend/default/blank/template/catalog/product/compare/sidebar.phtml b/app/design/frontend/default/blank/template/catalog/product/compare/sidebar.phtml index 368a2ec873..156456c083 100644 --- a/app/design/frontend/default/blank/template/catalog/product/compare/sidebar.phtml +++ b/app/design/frontend/default/blank/template/catalog/product/compare/sidebar.phtml @@ -47,7 +47,7 @@ __('Clear All') ?> - __('Compare Items') ?> + __('Compare Items') ?> __('You have no items to compare.') ?> diff --git a/app/design/frontend/default/blank/template/catalog/product/list.phtml b/app/design/frontend/default/blank/template/catalog/product/list.phtml index 858576b3c5..9fef17a66b 100644 --- a/app/design/frontend/default/blank/template/catalog/product/list.phtml +++ b/app/design/frontend/default/blank/template/catalog/product/list.phtml @@ -56,7 +56,7 @@ getPriceHtml($_product, true) ?> isSaleable()): ?> - __('Add to Cart') ?> + __('Add to Cart') ?> __('Out of stock') ?> @@ -98,7 +98,7 @@ getPriceHtml($_product, true) ?> isSaleable()): ?> - __('Add to Cart') ?> + __('Add to Cart') ?> __('Out of stock') ?> diff --git a/app/design/frontend/default/blank/template/catalog/product/new.phtml b/app/design/frontend/default/blank/template/catalog/product/new.phtml index 7bf85d39ae..d052b9391c 100644 --- a/app/design/frontend/default/blank/template/catalog/product/new.phtml +++ b/app/design/frontend/default/blank/template/catalog/product/new.phtml @@ -38,7 +38,7 @@ getReviewsSummaryHtml($_product, 'short') ?> getPriceHtml($_product, true, '-new') ?> isSaleable()): ?> - __('Add to Cart') ?> + __('Add to Cart') ?> __('Out of stock') ?> diff --git a/app/design/frontend/default/blank/template/catalog/product/view/addtocart.phtml b/app/design/frontend/default/blank/template/catalog/product/view/addtocart.phtml index fca8b5f0b3..cfa374cc0c 100644 --- a/app/design/frontend/default/blank/template/catalog/product/view/addtocart.phtml +++ b/app/design/frontend/default/blank/template/catalog/product/view/addtocart.phtml @@ -34,6 +34,6 @@ __('Qty') ?>: - __('Add to Cart') ?> + __('Add to Cart') ?> \ No newline at end of file diff --git a/app/design/frontend/default/blank/template/catalogsearch/advanced/form.phtml b/app/design/frontend/default/blank/template/catalogsearch/advanced/form.phtml index e91a3e98d2..9438ffa4ac 100644 --- a/app/design/frontend/default/blank/template/catalogsearch/advanced/form.phtml +++ b/app/design/frontend/default/blank/template/catalogsearch/advanced/form.phtml @@ -78,9 +78,8 @@ - * __('Required Fields') ?> - __('Search') ?> + __('Search') ?> diff --git a/app/design/frontend/default/blank/template/catalogsearch/form.mini.phtml b/app/design/frontend/default/blank/template/catalogsearch/form.mini.phtml index 17a2f81c85..493df95c82 100644 --- a/app/design/frontend/default/blank/template/catalogsearch/form.mini.phtml +++ b/app/design/frontend/default/blank/template/catalogsearch/form.mini.phtml @@ -29,7 +29,7 @@ __('Search Site') ?> __('Search:') ?> - __('Search') ?> + __('Search') ?> « __('Back to Shopping Cart') ?> - isContinueDisabled()):?> disabled="disabled">__('Continue to Shipping Information') ?> + isContinueDisabled()):?> disabled="disabled">__('Continue to Shipping Information') ?> diff --git a/app/design/frontend/default/blank/template/checkout/multishipping/billing.phtml b/app/design/frontend/default/blank/template/checkout/multishipping/billing.phtml index 68b6dcaf68..f37c2926f8 100644 --- a/app/design/frontend/default/blank/template/checkout/multishipping/billing.phtml +++ b/app/design/frontend/default/blank/template/checkout/multishipping/billing.phtml @@ -76,7 +76,7 @@ « __('Back to Shipping Information') ?> - __('Continue to Review Your Order') ?> + __('Continue to Review Your Order') ?> diff --git a/app/design/frontend/default/blank/template/checkout/onepage/link.phtml b/app/design/frontend/default/blank/template/checkout/onepage/link.phtml index 7c245a842d..2c381434c1 100644 --- a/app/design/frontend/default/blank/template/checkout/onepage/link.phtml +++ b/app/design/frontend/default/blank/template/checkout/onepage/link.phtml @@ -26,6 +26,6 @@ ?> isPossibleOnepageCheckout()):?> - isDisabled()):?> disabled="disabled" onclick="window.location='getCheckoutUrl() ?>';">__('Proceed to Checkout') ?> + isDisabled()):?> disabled="disabled" onclick="window.location='getCheckoutUrl() ?>';">__('Proceed to Checkout') ?> diff --git a/app/design/frontend/default/blank/template/checkout/onepage/login.phtml b/app/design/frontend/default/blank/template/checkout/onepage/login.phtml index 42f8022a2a..6fce3491ef 100644 --- a/app/design/frontend/default/blank/template/checkout/onepage/login.phtml +++ b/app/design/frontend/default/blank/template/checkout/onepage/login.phtml @@ -108,13 +108,13 @@ - getQuote()->isAllowedGuestCheckout() ? $this->__('Continue') : $this->__('Register')) ?> + getQuote()->isAllowedGuestCheckout() ? $this->__('Continue') : $this->__('Register')) ?> __('Forgot your password?') ?> - __('Login') ?> + __('Login') ?> diff --git a/app/design/frontend/default/blank/template/checkout/onepage/payment.phtml b/app/design/frontend/default/blank/template/checkout/onepage/payment.phtml index 8219d8ca7b..79446b94aa 100644 --- a/app/design/frontend/default/blank/template/checkout/onepage/payment.phtml +++ b/app/design/frontend/default/blank/template/checkout/onepage/payment.phtml @@ -41,7 +41,7 @@ __('* Required Fields') ?> « __('Back') ?> - __('Continue') ?> + __('Continue') ?> __('Loading next step...') ?> diff --git a/app/design/frontend/default/blank/template/checkout/onepage/review.phtml b/app/design/frontend/default/blank/template/checkout/onepage/review.phtml index c663fcaaf4..620e3febe3 100644 --- a/app/design/frontend/default/blank/template/checkout/onepage/review.phtml +++ b/app/design/frontend/default/blank/template/checkout/onepage/review.phtml @@ -30,7 +30,7 @@ getChildHtml('agreements') ?> __('Forgot an Item?') ?> __('Edit Your Cart') ?> - __('Place Order') ?> + __('Place Order') ?> __('Submitting order information...') ?> diff --git a/app/design/frontend/default/blank/template/checkout/onepage/shipping.phtml b/app/design/frontend/default/blank/template/checkout/onepage/shipping.phtml index f8b16ec48a..26f686e8c2 100644 --- a/app/design/frontend/default/blank/template/checkout/onepage/shipping.phtml +++ b/app/design/frontend/default/blank/template/checkout/onepage/shipping.phtml @@ -91,7 +91,7 @@ __('* Required Fields') ?> « __('Back') ?> - __('Continue') ?> + __('Continue') ?> __('Loading next step...') ?> @@ -107,6 +107,6 @@ //shippingForm.setElementsRelation('shipping:country_id', 'shipping:region', 'getUrl('directory/json/childRegion') ?>', '__('Select State/Province...') ?>'); $('shipping-address-select') && shipping.newAddress(!$('shipping-address-select').value); - var shippingRegionUpdater = new RegionUpdater('shipping:country_id', 'shipping:region', 'shipping:region_id', countryRegions, undefined, helper('directory')->getCountriesWithOptionalZipJson() ?>); + var shippingRegionUpdater = new RegionUpdater('shipping:country_id', 'shipping:region', 'shipping:region_id', countryRegions, undefined, 'shipping:postcode'); //]]> diff --git a/app/design/frontend/default/blank/template/checkout/onepage/shipping_method.phtml b/app/design/frontend/default/blank/template/checkout/onepage/shipping_method.phtml index 0deb403107..2e3f9d4f14 100644 --- a/app/design/frontend/default/blank/template/checkout/onepage/shipping_method.phtml +++ b/app/design/frontend/default/blank/template/checkout/onepage/shipping_method.phtml @@ -39,7 +39,7 @@ « __('Back') ?> - __('Continue') ?> + __('Continue') ?> __('Loading next step...') ?> diff --git a/app/design/frontend/default/blank/template/checkout/success.phtml b/app/design/frontend/default/blank/template/checkout/success.phtml index e11b3252cf..b7e251d08e 100644 --- a/app/design/frontend/default/blank/template/checkout/success.phtml +++ b/app/design/frontend/default/blank/template/checkout/success.phtml @@ -42,5 +42,5 @@ - __('Continue Shopping') ?> + __('Continue Shopping') ?> diff --git a/app/design/frontend/default/blank/template/chronopay/info.phtml b/app/design/frontend/default/blank/template/chronopay/info.phtml index d566bab32d..c0e911ce48 100644 --- a/app/design/frontend/default/blank/template/chronopay/info.phtml +++ b/app/design/frontend/default/blank/template/chronopay/info.phtml @@ -28,8 +28,7 @@ getMethod()->getTitle() ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/blank/template/contacts/form.phtml b/app/design/frontend/default/blank/template/contacts/form.phtml index be10570419..c3bc2c748b 100644 --- a/app/design/frontend/default/blank/template/contacts/form.phtml +++ b/app/design/frontend/default/blank/template/contacts/form.phtml @@ -57,7 +57,7 @@ __('* Required Fields') ?> - __('Submit') ?> + __('Submit') ?> diff --git a/app/design/frontend/default/blank/template/customer/form/address.phtml b/app/design/frontend/default/blank/template/customer/form/address.phtml index 4874cdbbd6..6e42662b6c 100644 --- a/app/design/frontend/default/blank/template/customer/form/address.phtml +++ b/app/design/frontend/default/blank/template/customer/form/address.phtml @@ -111,13 +111,13 @@ __('* Required Fields') ?> « __('Back') ?> - __('Save Address') ?> + __('Save Address') ?> \ No newline at end of file + diff --git a/app/design/frontend/default/blank/template/customer/form/changepassword.phtml b/app/design/frontend/default/blank/template/customer/form/changepassword.phtml index 31a11e01bc..7a8b155c1d 100644 --- a/app/design/frontend/default/blank/template/customer/form/changepassword.phtml +++ b/app/design/frontend/default/blank/template/customer/form/changepassword.phtml @@ -50,7 +50,7 @@ __('* Required Fields') ?> « __('Back') ?> - __('Save Password') ?> + __('Save Password') ?> diff --git a/app/design/frontend/default/blank/template/customer/widget/gender.phtml b/app/design/frontend/default/blank/template/customer/widget/gender.phtml new file mode 100644 index 0000000000..ddee19ee58 --- /dev/null +++ b/app/design/frontend/default/blank/template/customer/widget/gender.phtml @@ -0,0 +1,34 @@ + +isRequired()) echo ' class="required"' ?>>isRequired()) echo '*' ?>__('Gender') ?> +getFieldParams() ?>"> + getAttribute('gender')->getSource()->getAllOptions();?> + getGender();?> + + > + + \ No newline at end of file diff --git a/app/design/frontend/default/blank/template/cybersource/form.phtml b/app/design/frontend/default/blank/template/cybersource/form.phtml index ca554f0c99..75b7c6102c 100644 --- a/app/design/frontend/default/blank/template/cybersource/form.phtml +++ b/app/design/frontend/default/blank/template/cybersource/form.phtml @@ -106,6 +106,7 @@ + diff --git a/app/design/frontend/default/blank/template/page/2columns-right.phtml b/app/design/frontend/default/blank/template/page/2columns-right.phtml index b5c704214f..3d53f64da7 100644 --- a/app/design/frontend/default/blank/template/page/2columns-right.phtml +++ b/app/design/frontend/default/blank/template/page/2columns-right.phtml @@ -41,7 +41,7 @@ getChildHtml('header') ?> getChildHtml('breadcrumbs') ?> - getChildHtml('right') ?> + getChildHtml('right') ?> getChildHtml('global_messages') ?> getChildHtml('content') ?> diff --git a/app/design/frontend/default/blank/template/page/print.phtml b/app/design/frontend/default/blank/template/page/print.phtml index 72bcbb64c4..1120f6e507 100644 --- a/app/design/frontend/default/blank/template/page/print.phtml +++ b/app/design/frontend/default/blank/template/page/print.phtml @@ -44,7 +44,7 @@ getChildHtml('content') ?> - __('Close Window') ?> + __('Close Window') ?> getAbsoluteFooter() ?> diff --git a/app/design/frontend/default/blank/template/paybox/direct/info.phtml b/app/design/frontend/default/blank/template/paybox/direct/info.phtml index 19624389db..d9d9a4c1f0 100644 --- a/app/design/frontend/default/blank/template/paybox/direct/info.phtml +++ b/app/design/frontend/default/blank/template/paybox/direct/info.phtml @@ -28,8 +28,7 @@ getMethod()->getTitle() ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> - __('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> - __('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> + __('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> diff --git a/app/design/frontend/default/blank/template/payment/info/cc.phtml b/app/design/frontend/default/blank/template/payment/info/cc.phtml index 8c619f73e1..d59fc95679 100644 --- a/app/design/frontend/default/blank/template/payment/info/cc.phtml +++ b/app/design/frontend/default/blank/template/payment/info/cc.phtml @@ -27,8 +27,7 @@ getInfo()): ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> - __('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> - __('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> + __('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> diff --git a/app/design/frontend/default/blank/template/payment/info/ccsave.phtml b/app/design/frontend/default/blank/template/payment/info/ccsave.phtml index 3ef7be67c9..01d200bf09 100644 --- a/app/design/frontend/default/blank/template/payment/info/ccsave.phtml +++ b/app/design/frontend/default/blank/template/payment/info/ccsave.phtml @@ -26,5 +26,4 @@ ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> diff --git a/app/design/frontend/default/blank/template/paypal/express/review.phtml b/app/design/frontend/default/blank/template/paypal/express/review.phtml index 306e60faac..2277b97eb7 100644 --- a/app/design/frontend/default/blank/template/paypal/express/review.phtml +++ b/app/design/frontend/default/blank/template/paypal/express/review.phtml @@ -75,7 +75,7 @@ - __('Update Shipping Method') ?> + __('Update Shipping Method') ?> @@ -104,7 +104,7 @@ - __('Place an Order') ?> + __('Place an Order') ?> __('Submitting order information...') ?> diff --git a/app/design/frontend/default/blank/template/paypaluk/direct/form.phtml b/app/design/frontend/default/blank/template/paypaluk/direct/form.phtml index a39ebc47c6..489c74d431 100644 --- a/app/design/frontend/default/blank/template/paypaluk/direct/form.phtml +++ b/app/design/frontend/default/blank/template/paypaluk/direct/form.phtml @@ -34,25 +34,31 @@ __('Switch/Solo Only') ?> * - - __('Issue Number') ?>: - - __('Start Date') ?>: - - getCcMonths() as $k=>$v): ?> - getInfoData('cc_ss_start_month')): ?> selected="selected"> - - - - getSsStartYears() as $k=>$v): ?> - getInfoData('cc_ss_start_year')): ?> selected="selected"> - - - - + + + __('Issue Number') ?>: + + + __('Start Date') ?>: + + + getCcMonths() as $k=>$v): ?> + getInfoData('cc_ss_start_month')): ?> selected="selected"> + + + + + + getSsStartYears() as $k=>$v): ?> + getInfoData('cc_ss_start_year')): ?> selected="selected"> + + + + + - + + diff --git a/app/design/frontend/default/blank/template/paypaluk/direct/info.phtml b/app/design/frontend/default/blank/template/paypaluk/direct/info.phtml index cb06ad7d09..f0d72f3397 100644 --- a/app/design/frontend/default/blank/template/paypaluk/direct/info.phtml +++ b/app/design/frontend/default/blank/template/paypaluk/direct/info.phtml @@ -28,7 +28,6 @@ __('Credit Card Type: %s', $this->getCcTypeName()) ?> __('Credit Card Number: xxxx-%s', $this->getInfo()->getCcLast4()) ?> - __('Expiration Date: %s/%s', $this->getCcExpMonth(), $this->getInfo()->getCcExpYear()) ?> getInfo()->getCcSsIssue()): ?> __("Switch/Solo card issue number: %s", $this->getInfo()->getCcSsIssue()) ?> diff --git a/app/design/frontend/default/blank/template/paypaluk/express/review.phtml b/app/design/frontend/default/blank/template/paypaluk/express/review.phtml index dcb84b4a52..d3910fdd73 100644 --- a/app/design/frontend/default/blank/template/paypaluk/express/review.phtml +++ b/app/design/frontend/default/blank/template/paypaluk/express/review.phtml @@ -75,10 +75,8 @@ - - __('Update Shipping Method') ?> + __('Update Shipping Method') ?> - @@ -105,7 +103,7 @@ __('Submitting order information...') ?> - __('Place an Order') ?> + __('Place an Order') ?> isSaleable()):?> - __('Add All to Cart') ?> + __('Add All to Cart') ?> diff --git a/app/design/frontend/default/blank/template/wishlist/sharing.phtml b/app/design/frontend/default/blank/template/wishlist/sharing.phtml index 52dad3df51..4beb2192a7 100644 --- a/app/design/frontend/default/blank/template/wishlist/sharing.phtml +++ b/app/design/frontend/default/blank/template/wishlist/sharing.phtml @@ -51,7 +51,7 @@ __('* Required Fields') ?> « __('Back')?> - __('Share Wishlist') ?> + __('Share Wishlist') ?> diff --git a/app/design/frontend/default/blank/template/wishlist/view.phtml b/app/design/frontend/default/blank/template/wishlist/view.phtml index 672fbf6482..eb226c12d0 100644 --- a/app/design/frontend/default/blank/template/wishlist/view.phtml +++ b/app/design/frontend/default/blank/template/wishlist/view.phtml @@ -68,7 +68,7 @@ isSaleable()): ?> - __('Add to Cart') ?> + __('Add to Cart') ?> @@ -80,11 +80,11 @@ - __('Share Wishlist') ?> + __('Share Wishlist') ?> isSaleable()):?> - __('Add All to Cart') ?> + __('Add All to Cart') ?> - __('Update Wishlist') ?> + __('Update Wishlist') ?> diff --git a/app/design/frontend/default/default/layout/checkout.xml b/app/design/frontend/default/default/layout/checkout.xml index e2075b77bb..f6be52be0a 100644 --- a/app/design/frontend/default/default/layout/checkout.xml +++ b/app/design/frontend/default/default/layout/checkout.xml @@ -48,6 +48,9 @@ Default layout, loads most of the pages configurablecheckout/cart_item_renderer_configurablecheckout/cart/sidebar/default.phtml + + + diff --git a/app/design/frontend/default/default/layout/customer.xml b/app/design/frontend/default/default/layout/customer.xml index aa01cc5684..320ee93024 100644 --- a/app/design/frontend/default/default/layout/customer.xml +++ b/app/design/frontend/default/default/layout/customer.xml @@ -119,6 +119,9 @@ New customer registration + + + page/1column.phtml @@ -238,6 +241,9 @@ Customer account address edit page + + + diff --git a/app/design/frontend/default/default/layout/ogone.xml b/app/design/frontend/default/default/layout/ogone.xml new file mode 100644 index 0000000000..d498775aa5 --- /dev/null +++ b/app/design/frontend/default/default/layout/ogone.xml @@ -0,0 +1,47 @@ + + + + + + + + page/1column.phtml + + + + + + + + + + + + + + diff --git a/app/design/frontend/default/default/template/callouts/right_col.phtml b/app/design/frontend/default/default/template/callouts/right_col.phtml index d812eed9a9..c34cdf544d 100644 --- a/app/design/frontend/default/default/template/callouts/right_col.phtml +++ b/app/design/frontend/default/default/template/callouts/right_col.phtml @@ -26,5 +26,5 @@ ?> - + diff --git a/app/design/frontend/default/default/template/catalog/layer/filter.phtml b/app/design/frontend/default/default/template/catalog/layer/filter.phtml index 05770dd59d..96b995330a 100644 --- a/app/design/frontend/default/default/template/catalog/layer/filter.phtml +++ b/app/design/frontend/default/default/template/catalog/layer/filter.phtml @@ -35,7 +35,10 @@ getItems() as $_item): ?> + getCount() > 0): ?> getLabel() ?> + getLabel() ?> + (getCount() ?>) diff --git a/app/design/frontend/default/default/template/catalog/navigation/top.phtml b/app/design/frontend/default/default/template/catalog/navigation/top.phtml index c009288e64..77efa554f4 100644 --- a/app/design/frontend/default/default/template/catalog/navigation/top.phtml +++ b/app/design/frontend/default/default/template/catalog/navigation/top.phtml @@ -24,7 +24,7 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> - __('Category Navigation:') ?> - - getStoreCategories() as $_category): ?> - drawItem($_category) ?> - - + getStoreCategories()) > 0): ?> + + getStoreCategories() as $_category): ?> + drawItem($_category) ?> + + + getChildHtml('topLeftLinks') ?> \ No newline at end of file diff --git a/app/design/frontend/default/default/template/catalog/product/compare/list.phtml b/app/design/frontend/default/default/template/catalog/product/compare/list.phtml index 15c3282651..76d5352035 100644 --- a/app/design/frontend/default/default/template/catalog/product/compare/list.phtml +++ b/app/design/frontend/default/default/template/catalog/product/compare/list.phtml @@ -76,7 +76,7 @@ getItems() as $_item): ?> - __($_attribute->getFrontendLabel()) ?> + getStoreLabel() ?> getAttributeCode()) { diff --git a/app/design/frontend/default/default/template/catalogsearch/advanced/form.phtml b/app/design/frontend/default/default/template/catalogsearch/advanced/form.phtml index 7b57f44f19..0349171d10 100644 --- a/app/design/frontend/default/default/template/catalogsearch/advanced/form.phtml +++ b/app/design/frontend/default/default/template/catalogsearch/advanced/form.phtml @@ -80,7 +80,6 @@ - * __('Required Fields') ?> __('Search') ?> diff --git a/app/design/frontend/default/default/template/checkout/onepage/billing.phtml b/app/design/frontend/default/default/template/checkout/onepage/billing.phtml index 9d321b237a..7a1aea971c 100644 --- a/app/design/frontend/default/default/template/checkout/onepage/billing.phtml +++ b/app/design/frontend/default/default/template/checkout/onepage/billing.phtml @@ -92,6 +92,14 @@ +getLayout()->createBlock('customer/widget_gender') ?> +isEnabled()): ?> + + setGender($this->getQuote()->getCustomerGender()) + ->setFieldIdFormat('billing:%s')->setFieldNameFormat('billing[%s]')->toHtml() ?> + + + __('Password') ?> * @@ -135,6 +143,6 @@ //billingForm.setElementsRelation('billing:country_id', 'billing:region', 'getUrl('directory/json/childRegion') ?>', '__('Select State/Province...') ?>'); $('billing-address-select') && billing.newAddress(!$('billing-address-select').value); - var billingRegionUpdater = new RegionUpdater('billing:country_id', 'billing:region', 'billing:region_id', countryRegions, undefined, helper('directory')->getCountriesWithOptionalZipJson() ?>); + var billingRegionUpdater = new RegionUpdater('billing:country_id', 'billing:region', 'billing:region_id', countryRegions, undefined, 'billing:postcode'); //]]> diff --git a/app/design/frontend/default/default/template/checkout/onepage/shipping.phtml b/app/design/frontend/default/default/template/checkout/onepage/shipping.phtml index d4cd2a38d9..580c8c83f9 100644 --- a/app/design/frontend/default/default/template/checkout/onepage/shipping.phtml +++ b/app/design/frontend/default/default/template/checkout/onepage/shipping.phtml @@ -103,6 +103,6 @@ //shippingForm.setElementsRelation('shipping:country_id', 'shipping:region', 'getUrl('directory/json/childRegion') ?>', '__('Select State/Province...') ?>'); $('shipping-address-select') && shipping.newAddress(!$('shipping-address-select').value); - var shippingRegionUpdater = new RegionUpdater('shipping:country_id', 'shipping:region', 'shipping:region_id', countryRegions, undefined, helper('directory')->getCountriesWithOptionalZipJson() ?>); + var shippingRegionUpdater = new RegionUpdater('shipping:country_id', 'shipping:region', 'shipping:region_id', countryRegions, undefined, 'shipping:postcode'); //]]> diff --git a/app/design/frontend/default/default/template/chronopay/info.phtml b/app/design/frontend/default/default/template/chronopay/info.phtml index 3b494c64df..849185b083 100644 --- a/app/design/frontend/default/default/template/chronopay/info.phtml +++ b/app/design/frontend/default/default/template/chronopay/info.phtml @@ -28,8 +28,7 @@ getMethod()->getTitle() ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/default/template/customer/address/edit.phtml b/app/design/frontend/default/default/template/customer/address/edit.phtml index 236ee15b08..9e19e3addd 100644 --- a/app/design/frontend/default/default/template/customer/address/edit.phtml +++ b/app/design/frontend/default/default/template/customer/address/edit.phtml @@ -131,5 +131,5 @@ diff --git a/app/design/frontend/default/default/template/customer/form/address.phtml b/app/design/frontend/default/default/template/customer/form/address.phtml index e0159ee5e3..89f89aff6f 100644 --- a/app/design/frontend/default/default/template/customer/form/address.phtml +++ b/app/design/frontend/default/default/template/customer/form/address.phtml @@ -117,5 +117,5 @@ var dataForm = new VarienForm('form-validate', true); //dataForm.setElementsRelation('country', 'state', 'getUrl('directory/json/childRegion') ?>'); - new RegionUpdater('country', 'region', 'region_id', countryRegions, undefined, helper('directory')->getCountriesWithOptionalZipJson() ?>); - \ No newline at end of file + new RegionUpdater('country', 'region', 'region_id', countryRegions, undefined, 'zip'); + diff --git a/app/design/frontend/default/default/template/customer/form/edit.phtml b/app/design/frontend/default/default/template/customer/form/edit.phtml index cc5e32f48b..308784eeb3 100644 --- a/app/design/frontend/default/default/template/customer/form/edit.phtml +++ b/app/design/frontend/default/default/template/customer/form/edit.phtml @@ -48,6 +48,10 @@ getLayout()->createBlock('customer/widget_taxvat') ?> isEnabled()): ?> setTaxvat($this->getCustomer()->getTaxvat())->toHtml() ?> + +getLayout()->createBlock('customer/widget_gender') ?> +isEnabled()): ?> + setGender($this->getCustomer()->getGender())->toHtml() ?> diff --git a/app/design/frontend/default/default/template/customer/form/register.phtml b/app/design/frontend/default/default/template/customer/form/register.phtml index 2c50c240c8..e9c86f3b58 100644 --- a/app/design/frontend/default/default/template/customer/form/register.phtml +++ b/app/design/frontend/default/default/template/customer/form/register.phtml @@ -63,6 +63,10 @@ getLayout()->createBlock('customer/widget_taxvat') ?> isEnabled()): ?> setTaxvat($this->getFormData()->getTaxvat())->toHtml() ?> + +getLayout()->createBlock('customer/widget_gender') ?> +isEnabled()): ?> + setGender($this->getFormData()->getGender())->toHtml() ?> @@ -146,7 +150,7 @@ //getShowAddressFields()): ?> - new RegionUpdater('country', 'region', 'region_id', helper('directory')->getRegionJson() ?>, undefined, helper('directory')->getCountriesWithOptionalZipJson() ?>); + new RegionUpdater('country', 'region', 'region_id', helper('directory')->getRegionJson() ?>, undefined, 'zip'); //]]> - \ No newline at end of file + diff --git a/app/design/frontend/default/default/template/customer/widget/gender.phtml b/app/design/frontend/default/default/template/customer/widget/gender.phtml new file mode 100644 index 0000000000..7075b30a87 --- /dev/null +++ b/app/design/frontend/default/default/template/customer/widget/gender.phtml @@ -0,0 +1,34 @@ + +isRequired()) echo ' class="required"' ?>>isRequired()) echo '*' ?>__('Gender') ?> +getFieldParams() ?>"> + getAttribute('gender')->getSource()->getAllOptions();?> + getGender();?> + + > + + \ No newline at end of file diff --git a/app/design/frontend/default/default/template/cybersource/form.phtml b/app/design/frontend/default/default/template/cybersource/form.phtml index 5399e03dd9..3f288a42f9 100644 --- a/app/design/frontend/default/default/template/cybersource/form.phtml +++ b/app/design/frontend/default/default/template/cybersource/form.phtml @@ -83,7 +83,7 @@ __('Switch/Solo/Maestro(UK Domestic) Only') ?> * - + __('Issue Number') ?>: @@ -107,6 +107,7 @@ + diff --git a/app/design/frontend/default/default/template/eway/info.phtml b/app/design/frontend/default/default/template/eway/info.phtml index 045d64a8a5..945cc5d147 100644 --- a/app/design/frontend/default/default/template/eway/info.phtml +++ b/app/design/frontend/default/default/template/eway/info.phtml @@ -27,8 +27,7 @@ getInfo()): ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/default/template/flo2cash/info.phtml b/app/design/frontend/default/default/template/flo2cash/info.phtml index b6c27bfca7..0a3b2be499 100644 --- a/app/design/frontend/default/default/template/flo2cash/info.phtml +++ b/app/design/frontend/default/default/template/flo2cash/info.phtml @@ -28,8 +28,7 @@ getMethod()->getTitle() ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/default/template/giftmessage/inline.phtml b/app/design/frontend/default/default/template/giftmessage/inline.phtml index bc6fbcae90..aa7327a318 100644 --- a/app/design/frontend/default/default/template/giftmessage/inline.phtml +++ b/app/design/frontend/default/default/template/giftmessage/inline.phtml @@ -32,7 +32,7 @@ if(!window.toogleVisibilityOnObjects) { if($(source) && $(source).checked) { objects.each(function(item){ $(item).show(); - $(item).getElementsByClassName('input-text').each(function(item) { + $$('#' + item + ' .input-text').each(function(item) { item.removeClassName('validation-passed'); }); }); @@ -41,11 +41,11 @@ if(!window.toogleVisibilityOnObjects) { } else { objects.each(function(item){ $(item).hide(); - $(item).getElementsBySelector('.input-text').each(function(sitem) { + $$('#' + item + ' .input-text').each(function(sitem) { sitem.addClassName('validation-passed'); }); - $(item).getElementsBySelector('.giftmessage-area').each(function(sitem) { + $$('#' + item + ' .giftmessage-area').each(function(sitem) { sitem.value = ''; }); }); diff --git a/app/design/frontend/default/default/template/newsletter/subscribe.phtml b/app/design/frontend/default/default/template/newsletter/subscribe.phtml index 883bb4e26b..aaf36cab8e 100644 --- a/app/design/frontend/default/default/template/newsletter/subscribe.phtml +++ b/app/design/frontend/default/default/template/newsletter/subscribe.phtml @@ -28,7 +28,7 @@ __('Newsletter') ?> - + __('Newsletter') ?> - + getChildHtml('right') ?> diff --git a/app/design/frontend/default/default/template/paybox/direct/info.phtml b/app/design/frontend/default/default/template/paybox/direct/info.phtml index 1f257d6c67..6de67d2029 100644 --- a/app/design/frontend/default/default/template/paybox/direct/info.phtml +++ b/app/design/frontend/default/default/template/paybox/direct/info.phtml @@ -28,8 +28,7 @@ getMethod()->getTitle() ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/default/template/payment/info/cc.phtml b/app/design/frontend/default/default/template/payment/info/cc.phtml index c69c0af319..5b8b30c23f 100644 --- a/app/design/frontend/default/default/template/payment/info/cc.phtml +++ b/app/design/frontend/default/default/template/payment/info/cc.phtml @@ -27,8 +27,7 @@ getInfo()): ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/default/template/payment/info/ccsave.phtml b/app/design/frontend/default/default/template/payment/info/ccsave.phtml index 97725c62fc..9af84737e4 100644 --- a/app/design/frontend/default/default/template/payment/info/ccsave.phtml +++ b/app/design/frontend/default/default/template/payment/info/ccsave.phtml @@ -26,5 +26,4 @@ ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> diff --git a/app/design/frontend/default/default/template/paypaluk/direct/form.phtml b/app/design/frontend/default/default/template/paypaluk/direct/form.phtml index d6dc8d2f50..1d1bbe4971 100644 --- a/app/design/frontend/default/default/template/paypaluk/direct/form.phtml +++ b/app/design/frontend/default/default/template/paypaluk/direct/form.phtml @@ -34,25 +34,31 @@ __('Switch/Solo Only') ?> * - - __('Issue Number') ?>: - - __('Start Date') ?>: - - getCcMonths() as $k=>$v): ?> - getInfoData('cc_ss_start_month')): ?> selected="selected"> - - - - getSsStartYears() as $k=>$v): ?> - getInfoData('cc_ss_start_year')): ?> selected="selected"> - - + + + __('Issue Number') ?>: + + + __('Start Date') ?>: + + + getCcMonths() as $k=>$v): ?> + getInfoData('cc_ss_start_month')): ?> selected="selected"> + + + + + + getSsStartYears() as $k=>$v): ?> + getInfoData('cc_ss_start_year')): ?> selected="selected"> + + + - + + + diff --git a/app/design/frontend/default/default/template/paypaluk/direct/info.phtml b/app/design/frontend/default/default/template/paypaluk/direct/info.phtml index 2b812337ea..5a76d3b6c7 100644 --- a/app/design/frontend/default/default/template/paypaluk/direct/info.phtml +++ b/app/design/frontend/default/default/template/paypaluk/direct/info.phtml @@ -28,7 +28,6 @@ __('Credit Card Type: %s', $this->getCcTypeName()) ?> __('Credit Card Number: xxxx-%s', $this->getInfo()->getCcLast4()) ?> -__('Expiration Date: %s/%s', $this->getCcExpMonth(), $this->getInfo()->getCcExpYear()) ?> getInfo()->getCcSsIssue()): ?> __("Switch/Solo card issue number: %s", $this->getInfo()->getCcSsIssue()) ?> diff --git a/app/design/frontend/default/default/template/reports/product/widget/compared.phtml b/app/design/frontend/default/default/template/reports/product/widget/compared.phtml new file mode 100644 index 0000000000..814dba0396 --- /dev/null +++ b/app/design/frontend/default/default/template/reports/product/widget/compared.phtml @@ -0,0 +1,67 @@ + + +getRecentlyComparedProducts()): ?> + + __('Recently Compared Products') ?> + + getItems() as $_product): ?> + 5): continue; endif; ?> + + + + + + htmlEscape($_product->getName()) ?> + getReviewsSummaryHtml($_product, 'short') ?> + getPriceHtml($_product, true, '-new') ?> + + isSaleable()): ?> + __('Add to Cart') ?> + + getIsInStock()): ?> + __('In stock') ?> + + __('Out of stock') ?> + + + + helper('wishlist')->isAllow()) : ?> + __('Add to Wishlist') ?> + + + + + + + + + diff --git a/app/design/frontend/default/default/template/reports/product/widget/viewed.phtml b/app/design/frontend/default/default/template/reports/product/widget/viewed.phtml new file mode 100644 index 0000000000..3da1958b9f --- /dev/null +++ b/app/design/frontend/default/default/template/reports/product/widget/viewed.phtml @@ -0,0 +1,70 @@ + + +getRecentlyViewedProducts()): ?> + + __('Recently Viewed Products') ?> + + getItems() as $_product): ?> + 5): continue; endif; ?> + + + + + + htmlEscape($_product->getName()) ?> + getReviewsSummaryHtml($_product, 'short') ?> + getPriceHtml($_product, true, '-new') ?> + + isSaleable()): ?> + __('Add to Cart') ?> + + getIsInStock()): ?> + __('In stock') ?> + + __('Out of stock') ?> + + + + helper('wishlist')->isAllow()) : ?> + __('Add to Wishlist') ?> + + getAddToCompareUrl($_product)): ?> + | __('Add to Compare') ?> + + + + + + + + + diff --git a/app/design/frontend/default/default/template/sales/order/shipment/items.phtml b/app/design/frontend/default/default/template/sales/order/shipment/items.phtml index 9223edad46..1531a20c6d 100644 --- a/app/design/frontend/default/default/template/sales/order/shipment/items.phtml +++ b/app/design/frontend/default/default/template/sales/order/shipment/items.phtml @@ -27,7 +27,7 @@ getOrder() ?> getTracksCollection()->count()) : ?> -__('Track all shipments') ?> +__('Track all shipments') ?> __('Print All Shipments') ?> @@ -43,7 +43,7 @@ - __('Track this shipment') ?> + __('Track this shipment') ?> @@ -56,7 +56,7 @@ isCustom()): ?> getNumber() ?> - getNumber() ?> + getNumber() ?> , getOrder() ?> __('Items Ordered') ?> getTracksCollection()->count()) : ?> - | __('Track your order') ?> + | __('Track your order') ?> diff --git a/app/design/frontend/default/default/template/sales/reorder/sidebar.phtml b/app/design/frontend/default/default/template/sales/reorder/sidebar.phtml index 437766b1f3..f8adcebe44 100644 --- a/app/design/frontend/default/default/template/sales/reorder/sidebar.phtml +++ b/app/design/frontend/default/default/template/sales/reorder/sidebar.phtml @@ -33,7 +33,7 @@ ?> getLastOrder()): ?> - + __('My Orders') ?> - __('View All') ?> diff --git a/app/design/frontend/default/default/template/tag/customer/view.phtml b/app/design/frontend/default/default/template/tag/customer/view.phtml index 3954c1d96b..b6c6f45ef4 100644 --- a/app/design/frontend/default/default/template/tag/customer/view.phtml +++ b/app/design/frontend/default/default/template/tag/customer/view.phtml @@ -31,9 +31,7 @@ getMessagesBlock()->getGroupedHtml() ?> __('Tag Name: %s', $this->htmlEscape($this->getTagInfo()->getName())) ?> - ( __('Edit Tag') ?> - | - __('Delete Tag') ?> ) + __('Delete') ?> getToolbarHtml() ?> diff --git a/app/design/frontend/default/default/template/wishlist/view.phtml b/app/design/frontend/default/default/template/wishlist/view.phtml index d221d4ad5c..f55932e653 100644 --- a/app/design/frontend/default/default/template/wishlist/view.phtml +++ b/app/design/frontend/default/default/template/wishlist/view.phtml @@ -75,7 +75,7 @@ - __('Share Wishlist') ?> + __('Share Wishlist') ?> isSaleable()):?> __('Add All to Cart') ?> diff --git a/app/design/frontend/default/iphone/layout/checkout.xml b/app/design/frontend/default/iphone/layout/checkout.xml index 048688c335..6df336b78a 100644 --- a/app/design/frontend/default/iphone/layout/checkout.xml +++ b/app/design/frontend/default/iphone/layout/checkout.xml @@ -44,6 +44,9 @@ Default layout, loads most of the pages + + + diff --git a/app/design/frontend/default/iphone/layout/customer.xml b/app/design/frontend/default/iphone/layout/customer.xml index 33acd783a1..061aa625e4 100644 --- a/app/design/frontend/default/iphone/layout/customer.xml +++ b/app/design/frontend/default/iphone/layout/customer.xml @@ -1,216 +1,222 @@ - - - - - - - - Catalog{{baseUrl}}Catalog9 - - - AccountMy Account2 - - - Searchjavascript:void(0);>Search0onClick="showSearchForm(1);" - - - - - - Log OutLog Out1 - - - - - - Log InLog In1 - - - - - - - - - - - - page/1column.phtml - - - - - - - - - - - - - page/1column.phtml - - - - - - - - - - - - - page/1column.phtml - - - - - - - - - - - - page/1column.phtml - Password forgotten - - - - - - - - - - - Edit Account Info - - - - - - - left.permanent.callout - - - - - - - page/1column.phtml - - - - - accountcustomer/account/Account Dashboard - account_editcustomer/account/edit/Account Information - address_bookcustomer/address/Address Book - - - - - - - tags_popular - - - - - - - - - page/1column.phtml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + Catalog{{baseUrl}}Catalog9 + + + AccountMy Account2 + + + Searchjavascript:void(0);>Search0onClick="showSearchForm(1);" + + + + + + Log OutLog Out1 + + + + + + Log InLog In1 + + + + + + + + + + + + page/1column.phtml + + + + + + + + + + + + + page/1column.phtml + + + + + + + + + + + + + + + + page/1column.phtml + + + + + + + + + + + + page/1column.phtml + Password forgotten + + + + + + + + + + + Edit Account Info + + + + + + + left.permanent.callout + + + + + + + page/1column.phtml + + + + + accountcustomer/account/Account Dashboard + account_editcustomer/account/edit/Account Information + address_bookcustomer/address/Address Book + + + + + + + tags_popular + + + + + + + + + page/1column.phtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/design/frontend/default/iphone/layout/ogone.xml b/app/design/frontend/default/iphone/layout/ogone.xml new file mode 100644 index 0000000000..3b4785b540 --- /dev/null +++ b/app/design/frontend/default/iphone/layout/ogone.xml @@ -0,0 +1,47 @@ + + + + + + + + page/1column.phtml + + + + + + + + + + + + + + diff --git a/app/design/frontend/default/iphone/template/catalog/navigation/top.phtml b/app/design/frontend/default/iphone/template/catalog/navigation/top.phtml index fc59f83712..166a31dfa9 100644 --- a/app/design/frontend/default/iphone/template/catalog/navigation/top.phtml +++ b/app/design/frontend/default/iphone/template/catalog/navigation/top.phtml @@ -1,42 +1,44 @@ - - - - - - getStoreCategories() as $_category): ?> - drawItem($_category) ?> - - - + + + + + getStoreCategories()) > 0): ?> + + getStoreCategories() as $_category): ?> + drawItem($_category) ?> + + + + \ No newline at end of file diff --git a/app/design/frontend/default/iphone/template/catalog/product/compare/list.phtml b/app/design/frontend/default/iphone/template/catalog/product/compare/list.phtml index c4a7dadb5f..534f67afd9 100644 --- a/app/design/frontend/default/iphone/template/catalog/product/compare/list.phtml +++ b/app/design/frontend/default/iphone/template/catalog/product/compare/list.phtml @@ -76,7 +76,7 @@ getItems() as $_item): ?> - getFrontendLabel() ?> + getStoreLabel() ?> getAttributeCode()) { diff --git a/app/design/frontend/default/iphone/template/catalogsearch/advanced/form.phtml b/app/design/frontend/default/iphone/template/catalogsearch/advanced/form.phtml index d80ded8d8d..9d2f0bed81 100644 --- a/app/design/frontend/default/iphone/template/catalogsearch/advanced/form.phtml +++ b/app/design/frontend/default/iphone/template/catalogsearch/advanced/form.phtml @@ -79,7 +79,6 @@ - * __('Required Fields') ?> __('Search') ?> diff --git a/app/design/frontend/default/iphone/template/checkout/onepage/billing.phtml b/app/design/frontend/default/iphone/template/checkout/onepage/billing.phtml index ca47ecdc58..e12ebab2fe 100644 --- a/app/design/frontend/default/iphone/template/checkout/onepage/billing.phtml +++ b/app/design/frontend/default/iphone/template/checkout/onepage/billing.phtml @@ -143,6 +143,14 @@ + getLayout()->createBlock('customer/widget_gender') ?> + isEnabled()): ?> + + setGender($this->getQuote()->getCustomerGender()) + ->setFieldIdFormat('billing:%s')->setFieldNameFormat('billing[%s]')->toHtml() ?> + + + __('Password') ?> * @@ -202,10 +210,6 @@ //billingForm.setElementsRelation('billing:country_id', 'billing:region', 'getUrl('directory/json/childRegion') ?>', '__('Select State/Province...') ?>'); $('billing-address-select') && billing.newAddress(!$('billing-address-select').value); - var zipOptions = {}; - zipOptions.input_el = "billing:postcode"; - zipOptions.label_el = $("billing:postcode").up(1).down('label > span.required'); - zipOptions.optional_countries = helper('directory')->getCountriesWithOptionalZipJson() ?>; - var billingRegionUpdater = new RegionUpdater('billing:country_id', 'billing:region', 'billing:region_id', countryRegions, undefined, zipOptions); + var billingRegionUpdater = new RegionUpdater('billing:country_id', 'billing:region', 'billing:region_id', countryRegions, undefined, 'billing:postcode'); //--> \ No newline at end of file diff --git a/app/design/frontend/default/iphone/template/checkout/onepage/shipping.phtml b/app/design/frontend/default/iphone/template/checkout/onepage/shipping.phtml index c894735831..4e0ae3ccf2 100644 --- a/app/design/frontend/default/iphone/template/checkout/onepage/shipping.phtml +++ b/app/design/frontend/default/iphone/template/checkout/onepage/shipping.phtml @@ -171,10 +171,6 @@ //shippingForm.setElementsRelation('shipping:country_id', 'shipping:region', 'getUrl('directory/json/childRegion') ?>', '__('Select State/Province...') ?>'); $('shipping-address-select') && shipping.newAddress(!$('shipping-address-select').value); - var zipOptions = {}; - zipOptions.input_el = "shipping:postcode"; - zipOptions.label_el = $("shipping:postcode").up(1).down('label > span.required'); - zipOptions.optional_countries = helper('directory')->getCountriesWithOptionalZipJson() ?>; - var shippingRegionUpdater = new RegionUpdater('shipping:country_id', 'shipping:region', 'shipping:region_id', countryRegions, undefined, zipOptions); + var shippingRegionUpdater = new RegionUpdater('shipping:country_id', 'shipping:region', 'shipping:region_id', countryRegions, undefined, 'shipping:postcode'); //--> \ No newline at end of file diff --git a/app/design/frontend/default/iphone/template/chronopay/info.phtml b/app/design/frontend/default/iphone/template/chronopay/info.phtml index 3b494c64df..849185b083 100644 --- a/app/design/frontend/default/iphone/template/chronopay/info.phtml +++ b/app/design/frontend/default/iphone/template/chronopay/info.phtml @@ -28,8 +28,7 @@ getMethod()->getTitle() ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/iphone/template/customer/address/edit.phtml b/app/design/frontend/default/iphone/template/customer/address/edit.phtml index 8a7fcf6697..b2baf76062 100644 --- a/app/design/frontend/default/iphone/template/customer/address/edit.phtml +++ b/app/design/frontend/default/iphone/template/customer/address/edit.phtml @@ -161,9 +161,5 @@ diff --git a/app/design/frontend/default/iphone/template/customer/form/address.phtml b/app/design/frontend/default/iphone/template/customer/form/address.phtml index 331cbe3754..8f1be955ba 100644 --- a/app/design/frontend/default/iphone/template/customer/form/address.phtml +++ b/app/design/frontend/default/iphone/template/customer/form/address.phtml @@ -122,5 +122,5 @@ var dataForm = new VarienForm('form-validate', true); //dataForm.setElementsRelation('country', 'state', 'getUrl('directory/json/childRegion') ?>'); - new RegionUpdater('country', 'region', 'region_id', countryRegions, undefined, helper('directory')->getCountriesWithOptionalZipJson() ?>); - \ No newline at end of file + new RegionUpdater('country', 'region', 'region_id', countryRegions, undefined, 'zip'); + diff --git a/app/design/frontend/default/iphone/template/customer/form/edit.phtml b/app/design/frontend/default/iphone/template/customer/form/edit.phtml index c320331eaa..2ab3c62d6b 100644 --- a/app/design/frontend/default/iphone/template/customer/form/edit.phtml +++ b/app/design/frontend/default/iphone/template/customer/form/edit.phtml @@ -64,6 +64,10 @@ getLayout()->createBlock('customer/widget_taxvat') ?> isEnabled()): ?> setTaxvat($this->getCustomer()->getTaxvat())->toHtml() ?> + +getLayout()->createBlock('customer/widget_gender') ?> +isEnabled()): ?> + setGender($this->getCustomer()->getGender())->toHtml() ?> diff --git a/app/design/frontend/default/iphone/template/customer/form/register.phtml b/app/design/frontend/default/iphone/template/customer/form/register.phtml index b1c52d8482..015b63260a 100644 --- a/app/design/frontend/default/iphone/template/customer/form/register.phtml +++ b/app/design/frontend/default/iphone/template/customer/form/register.phtml @@ -79,6 +79,10 @@ getLayout()->createBlock('customer/widget_taxvat') ?> isEnabled()): ?> setTaxvat($this->getFormData()->getTaxvat())->toHtml() ?> + +getLayout()->createBlock('customer/widget_gender') ?> +isEnabled()): ?> + setGender($this->getFormData()->getGender())->toHtml() ?> @@ -181,10 +185,6 @@ \ No newline at end of file + diff --git a/app/design/frontend/default/iphone/template/customer/widget/gender.phtml b/app/design/frontend/default/iphone/template/customer/widget/gender.phtml new file mode 100644 index 0000000000..25a71c861f --- /dev/null +++ b/app/design/frontend/default/iphone/template/customer/widget/gender.phtml @@ -0,0 +1,34 @@ + +isRequired()) echo ' class="required"' ?>>isRequired()) echo '*' ?>__('Gender') ?> +getFieldParams() ?>"> + getAttribute('gender')->getSource()->getAllOptions();?> + getGender();?> + + > + + \ No newline at end of file diff --git a/app/design/frontend/default/iphone/template/cybersource/form.phtml b/app/design/frontend/default/iphone/template/cybersource/form.phtml index 57243395bb..ff61d030f9 100644 --- a/app/design/frontend/default/iphone/template/cybersource/form.phtml +++ b/app/design/frontend/default/iphone/template/cybersource/form.phtml @@ -59,41 +59,54 @@ __('Expiration Date') ?> * - - getInfoData('cc_exp_month') ?> - getCcMonths() as $k=>$v): ?> - selected="selected"> - - - getInfoData('cc_exp_year') ?> - - getCcYears() as $k=>$v): ?> - selected="selected"> - - + + + getInfoData('cc_exp_month') ?> + getCcMonths() as $k=>$v): ?> + selected="selected"> + + + + + getInfoData('cc_exp_year') ?> + + getCcYears() as $k=>$v): ?> + selected="selected"> + + + + hasSsCardType()): ?> __('Switch/Solo/Maestro(UK Domestic) Only') ?> * - - __('Issue Number') ?>: - - __('Start Date') ?>: - - getInfoData('cc_ss_start_month') ?> - getCcMonths() as $k=>$v): ?> - selected="selected"> - - - - getInfoData('cc_ss_start_year') ?> - getSsStartYears() as $k=>$v): ?> - selected="selected"> - - + + + __('Issue Number') ?>: + + + __('Start Date') ?>: + + + getInfoData('cc_ss_start_month') ?> + getCcMonths() as $k=>$v): ?> + selected="selected"> + + + + + + getInfoData('cc_ss_start_year') ?> + getSsStartYears() as $k=>$v): ?> + selected="selected"> + + + + + \ No newline at end of file diff --git a/app/design/frontend/default/iphone/template/cybersource/info.phtml b/app/design/frontend/default/iphone/template/cybersource/info.phtml index 09c61b10ff..0f53127497 100644 --- a/app/design/frontend/default/iphone/template/cybersource/info.phtml +++ b/app/design/frontend/default/iphone/template/cybersource/info.phtml @@ -29,7 +29,6 @@ __('Credit Card Type: %s', $this->getCcTypeName()) ?> __('Credit Card Number: xxxx-%s', $this->getInfo()->getCcLast4()) ?> -__('Expiration Date: %s/%s', $this->getCcExpMonth(), $this->getInfo()->getCcExpYear()) ?> getInfo()->getCcSsIssue()): ?> __("Switch/Solo/Maestro(UK Domestic) card issue number: %s", $this->getInfo()->getCcSsIssue()) ?> diff --git a/app/design/frontend/default/iphone/template/eway/info.phtml b/app/design/frontend/default/iphone/template/eway/info.phtml index 045d64a8a5..945cc5d147 100644 --- a/app/design/frontend/default/iphone/template/eway/info.phtml +++ b/app/design/frontend/default/iphone/template/eway/info.phtml @@ -27,8 +27,7 @@ getInfo()): ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/iphone/template/flo2cash/info.phtml b/app/design/frontend/default/iphone/template/flo2cash/info.phtml index b6c27bfca7..0a3b2be499 100644 --- a/app/design/frontend/default/iphone/template/flo2cash/info.phtml +++ b/app/design/frontend/default/iphone/template/flo2cash/info.phtml @@ -28,8 +28,7 @@ getMethod()->getTitle() ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/iphone/template/giftmessage/inline.phtml b/app/design/frontend/default/iphone/template/giftmessage/inline.phtml index 348e30b55d..aa84097c4a 100644 --- a/app/design/frontend/default/iphone/template/giftmessage/inline.phtml +++ b/app/design/frontend/default/iphone/template/giftmessage/inline.phtml @@ -32,7 +32,7 @@ if(!window.toogleVisibilityOnObjects) { if($(source) && $(source).checked) { objects.each(function(item){ $(item).show(); - Element.select($(item), '.input-text').each(function(item) { + $$('#' + item + ' .input-text').each(function(item) { item.removeClassName('validation-passed'); }); }); @@ -41,11 +41,11 @@ if(!window.toogleVisibilityOnObjects) { } else { objects.each(function(item){ $(item).hide(); - Element.select($(item), 'input-text').each(function(sitem) { + $$('#' + item + ' .input-text').each(function(sitem) { sitem.addClassName('validation-passed'); }); - Element.select($(item), 'giftmessage-area').each(function(sitem) { + $$('#' + item + ' .giftmessage-area').each(function(sitem) { sitem.value = ''; }); }); diff --git a/app/design/frontend/default/iphone/template/newsletter/subscribe.phtml b/app/design/frontend/default/iphone/template/newsletter/subscribe.phtml index 6cb04e7def..5e1b39c10e 100644 --- a/app/design/frontend/default/iphone/template/newsletter/subscribe.phtml +++ b/app/design/frontend/default/iphone/template/newsletter/subscribe.phtml @@ -28,7 +28,7 @@ __('Newsletter') ?> - + __('Newsletter') ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/iphone/template/payment/info/ccsave.phtml b/app/design/frontend/default/iphone/template/payment/info/ccsave.phtml index 97725c62fc..9af84737e4 100644 --- a/app/design/frontend/default/iphone/template/payment/info/ccsave.phtml +++ b/app/design/frontend/default/iphone/template/payment/info/ccsave.phtml @@ -26,5 +26,4 @@ ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> diff --git a/app/design/frontend/default/iphone/template/paypaluk/direct/form.phtml b/app/design/frontend/default/iphone/template/paypaluk/direct/form.phtml index d6dc8d2f50..09b2e6f7e9 100644 --- a/app/design/frontend/default/iphone/template/paypaluk/direct/form.phtml +++ b/app/design/frontend/default/iphone/template/paypaluk/direct/form.phtml @@ -34,25 +34,33 @@ __('Switch/Solo Only') ?> * - - __('Issue Number') ?>: - - __('Start Date') ?>: - - getCcMonths() as $k=>$v): ?> - getInfoData('cc_ss_start_month')): ?> selected="selected"> - - - - getSsStartYears() as $k=>$v): ?> - getInfoData('cc_ss_start_year')): ?> selected="selected"> - - + + + __('Issue Number') ?>: + + + + __('Start Date') ?>: + + + getCcMonths() as $k=>$v): ?> + getInfoData('cc_ss_start_month')): ?> selected="selected"> + + + + + + getSsStartYears() as $k=>$v): ?> + getInfoData('cc_ss_start_year')): ?> selected="selected"> + + + + - + + diff --git a/app/design/frontend/default/iphone/template/paypaluk/direct/info.phtml b/app/design/frontend/default/iphone/template/paypaluk/direct/info.phtml index 2b812337ea..5a76d3b6c7 100644 --- a/app/design/frontend/default/iphone/template/paypaluk/direct/info.phtml +++ b/app/design/frontend/default/iphone/template/paypaluk/direct/info.phtml @@ -28,7 +28,6 @@ __('Credit Card Type: %s', $this->getCcTypeName()) ?> __('Credit Card Number: xxxx-%s', $this->getInfo()->getCcLast4()) ?> -__('Expiration Date: %s/%s', $this->getCcExpMonth(), $this->getInfo()->getCcExpYear()) ?> getInfo()->getCcSsIssue()): ?> __("Switch/Solo card issue number: %s", $this->getInfo()->getCcSsIssue()) ?> diff --git a/app/design/frontend/default/iphone/template/sales/order/shipment.phtml b/app/design/frontend/default/iphone/template/sales/order/shipment.phtml index a711544194..6e2c147e19 100644 --- a/app/design/frontend/default/iphone/template/sales/order/shipment.phtml +++ b/app/design/frontend/default/iphone/template/sales/order/shipment.phtml @@ -58,7 +58,7 @@ function giftMessageToogle(giftMessageIdentifier) getOrder() ?> getTracksCollection()->count()) : ?> -__('Track all shipments') ?> +__('Track all shipments') ?> __('Print All Shipments') ?> @@ -74,26 +74,26 @@ function giftMessageToogle(giftMessageIdentifier) count()): ?> - __('Track this shipment') ?> + __('Track this shipment') ?> __('Tracking Number(s)') ?>: - count(); foreach($tracks as $track): ?> isCustom()): ?> getNumber() ?> - getNumber() ?> + getNumber() ?> , - + __('Items Shipped') ?> diff --git a/app/design/frontend/default/iphone/template/sales/order/view.phtml b/app/design/frontend/default/iphone/template/sales/order/view.phtml index ce7a5d608b..3bb2d08db7 100644 --- a/app/design/frontend/default/iphone/template/sales/order/view.phtml +++ b/app/design/frontend/default/iphone/template/sales/order/view.phtml @@ -58,7 +58,7 @@ function giftMessageToogle(giftMessageIdentifier) getOrder() ?> __('Items Ordered') ?> getTracksCollection()->count()) : ?> - | __('Track your order') ?> + | __('Track your order') ?> diff --git a/app/design/frontend/default/iphone/template/sales/reorder/sidebar.phtml b/app/design/frontend/default/iphone/template/sales/reorder/sidebar.phtml index 3d7bde7e6e..b450fbc3a7 100644 --- a/app/design/frontend/default/iphone/template/sales/reorder/sidebar.phtml +++ b/app/design/frontend/default/iphone/template/sales/reorder/sidebar.phtml @@ -36,7 +36,7 @@ getLastOrder()): ?> - + __('My Orders') ?> - __('View All'); ?> diff --git a/app/design/frontend/default/iphone/template/tag/customer/view.phtml b/app/design/frontend/default/iphone/template/tag/customer/view.phtml index f425089dd1..ac9e9abb34 100644 --- a/app/design/frontend/default/iphone/template/tag/customer/view.phtml +++ b/app/design/frontend/default/iphone/template/tag/customer/view.phtml @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> -TEST __('My Tags') ?> @@ -32,9 +31,7 @@ TEST getMessagesBlock()->getGroupedHtml() ?> __('Tag Name: %s', $this->htmlEscape($this->getTagInfo()->getName())) ?> - ( __('Edit Tag') ?> - | - __('Delete Tag') ?> ) + __('Delete') ?> getToolbarHtml() ?> @@ -49,8 +46,8 @@ TEST htmlEscape($_product->getName()) ?> - helper('catalog/product')->getPriceHtml($_product) ?> - helper('review/product')->getSummaryHtml($_product, 'short') ?> + getPriceHtml($_product) ?> + getSummaryHtml($_product, 'short') ?> getDescription() ?> diff --git a/app/design/frontend/default/iphone/template/wishlist/view.phtml b/app/design/frontend/default/iphone/template/wishlist/view.phtml index 11017c7e04..3b7d7c94da 100644 --- a/app/design/frontend/default/iphone/template/wishlist/view.phtml +++ b/app/design/frontend/default/iphone/template/wishlist/view.phtml @@ -71,7 +71,7 @@ - __('Share Wishlist') ?> + __('Share Wishlist') ?> isSaleable()):?> __('Add All to Cart') ?> diff --git a/app/design/frontend/default/modern/layout/checkout.xml b/app/design/frontend/default/modern/layout/checkout.xml index 138e4a3f27..97829b59ab 100644 --- a/app/design/frontend/default/modern/layout/checkout.xml +++ b/app/design/frontend/default/modern/layout/checkout.xml @@ -48,6 +48,9 @@ Default layout, loads most of the pages configurablecheckout/cart_item_renderer_configurablecheckout/cart/sidebar/default.phtml + + + diff --git a/app/design/frontend/default/modern/layout/customer.xml b/app/design/frontend/default/modern/layout/customer.xml index 1a92aa4e02..999f6992f9 100644 --- a/app/design/frontend/default/modern/layout/customer.xml +++ b/app/design/frontend/default/modern/layout/customer.xml @@ -113,6 +113,9 @@ New customer registration + + + page/1column.phtml @@ -222,6 +225,9 @@ Customer account address edit page + + + diff --git a/app/design/frontend/default/modern/layout/ogone.xml b/app/design/frontend/default/modern/layout/ogone.xml new file mode 100644 index 0000000000..3b4785b540 --- /dev/null +++ b/app/design/frontend/default/modern/layout/ogone.xml @@ -0,0 +1,47 @@ + + + + + + + + page/1column.phtml + + + + + + + + + + + + + + diff --git a/app/design/frontend/default/modern/template/callouts/right_col.phtml b/app/design/frontend/default/modern/template/callouts/right_col.phtml index d812eed9a9..c34cdf544d 100644 --- a/app/design/frontend/default/modern/template/callouts/right_col.phtml +++ b/app/design/frontend/default/modern/template/callouts/right_col.phtml @@ -26,5 +26,5 @@ ?> - + diff --git a/app/design/frontend/default/modern/template/catalog/navigation/top.phtml b/app/design/frontend/default/modern/template/catalog/navigation/top.phtml index a727828464..63def2a5f1 100644 --- a/app/design/frontend/default/modern/template/catalog/navigation/top.phtml +++ b/app/design/frontend/default/modern/template/catalog/navigation/top.phtml @@ -24,15 +24,17 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> - - - getStoreCategories() as $_category): ?> - drawItem($_category) ?> - - \ No newline at end of file +getStoreCategories()) > 0): ?> + + getStoreCategories() as $_category): ?> + drawItem($_category) ?> + + + diff --git a/app/design/frontend/default/modern/template/catalog/product/compare/list.phtml b/app/design/frontend/default/modern/template/catalog/product/compare/list.phtml index b12668ec67..1961403bb6 100644 --- a/app/design/frontend/default/modern/template/catalog/product/compare/list.phtml +++ b/app/design/frontend/default/modern/template/catalog/product/compare/list.phtml @@ -76,7 +76,7 @@ getItems() as $_item): ?> - getFrontendLabel() ?> + getStoreLabel() ?> getAttributeCode()) { diff --git a/app/design/frontend/default/modern/template/catalogsearch/advanced/form.phtml b/app/design/frontend/default/modern/template/catalogsearch/advanced/form.phtml index 07b43161f9..b8d673337a 100644 --- a/app/design/frontend/default/modern/template/catalogsearch/advanced/form.phtml +++ b/app/design/frontend/default/modern/template/catalogsearch/advanced/form.phtml @@ -82,7 +82,6 @@ - * __('Required Fields') ?> diff --git a/app/design/frontend/default/modern/template/checkout/onepage/billing.phtml b/app/design/frontend/default/modern/template/checkout/onepage/billing.phtml index 3cba76c072..6a78a838c0 100644 --- a/app/design/frontend/default/modern/template/checkout/onepage/billing.phtml +++ b/app/design/frontend/default/modern/template/checkout/onepage/billing.phtml @@ -88,6 +88,13 @@ setTaxvat($this->getQuote()->getCustomerTaxvat()) ->setFieldIdFormat('billing:%s')->setFieldNameFormat('billing[%s]')->toHtml() ?> + + getLayout()->createBlock('customer/widget_gender') ?> + isEnabled()): ?> + + setGender($this->getQuote()->getCustomerGender()) + ->setFieldIdFormat('billing:%s')->setFieldNameFormat('billing[%s]')->toHtml() ?> + @@ -130,6 +137,6 @@ //billingForm.setElementsRelation('billing:country_id', 'billing:region', 'getUrl('directory/json/childRegion') ?>', '__('Select State/Province...') ?>'); $('billing-address-select') && billing.newAddress(!$('billing-address-select').value); - var billingRegionUpdater = new RegionUpdater('billing:country_id', 'billing:region', 'billing:region_id', countryRegions, undefined, helper('directory')->getCountriesWithOptionalZipJson() ?>); + var billingRegionUpdater = new RegionUpdater('billing:country_id', 'billing:region', 'billing:region_id', countryRegions, undefined, 'billing:postcode'); //--> diff --git a/app/design/frontend/default/modern/template/checkout/onepage/shipping.phtml b/app/design/frontend/default/modern/template/checkout/onepage/shipping.phtml index 6c8505e0bf..6ca4f971cd 100644 --- a/app/design/frontend/default/modern/template/checkout/onepage/shipping.phtml +++ b/app/design/frontend/default/modern/template/checkout/onepage/shipping.phtml @@ -103,6 +103,6 @@ //shippingForm.setElementsRelation('shipping:country_id', 'shipping:region', 'getUrl('directory/json/childRegion') ?>', '__('Select State/Province...') ?>'); $('shipping-address-select') && shipping.newAddress(!$('shipping-address-select').value); - var shippingRegionUpdater = new RegionUpdater('shipping:country_id', 'shipping:region', 'shipping:region_id', countryRegions, undefined, helper('directory')->getCountriesWithOptionalZipJson() ?>); + var shippingRegionUpdater = new RegionUpdater('shipping:country_id', 'shipping:region', 'shipping:region_id', countryRegions, undefined, 'shipping:postcode'); //--> diff --git a/app/design/frontend/default/modern/template/chronopay/info.phtml b/app/design/frontend/default/modern/template/chronopay/info.phtml index 3b494c64df..849185b083 100644 --- a/app/design/frontend/default/modern/template/chronopay/info.phtml +++ b/app/design/frontend/default/modern/template/chronopay/info.phtml @@ -28,8 +28,7 @@ getMethod()->getTitle() ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/modern/template/customer/address/edit.phtml b/app/design/frontend/default/modern/template/customer/address/edit.phtml index eb8cce835c..b8cd7f3e54 100644 --- a/app/design/frontend/default/modern/template/customer/address/edit.phtml +++ b/app/design/frontend/default/modern/template/customer/address/edit.phtml @@ -137,6 +137,6 @@ \ No newline at end of file diff --git a/app/design/frontend/default/modern/template/customer/form/address.phtml b/app/design/frontend/default/modern/template/customer/form/address.phtml index 589734403e..aabb1c9280 100644 --- a/app/design/frontend/default/modern/template/customer/form/address.phtml +++ b/app/design/frontend/default/modern/template/customer/form/address.phtml @@ -118,6 +118,6 @@ var dataForm = new VarienForm('form-validate', true); //dataForm.setElementsRelation('country', 'state', 'getUrl('directory/json/childRegion') ?>'); - new RegionUpdater('country', 'region', 'region_id', countryRegions, undefined, helper('directory')->getCountriesWithOptionalZipJson() ?>); + new RegionUpdater('country', 'region', 'region_id', countryRegions, undefined, 'zip'); - \ No newline at end of file + diff --git a/app/design/frontend/default/modern/template/customer/form/edit.phtml b/app/design/frontend/default/modern/template/customer/form/edit.phtml index 4cbdc8b16e..a767509d0a 100644 --- a/app/design/frontend/default/modern/template/customer/form/edit.phtml +++ b/app/design/frontend/default/modern/template/customer/form/edit.phtml @@ -52,6 +52,10 @@ getLayout()->createBlock('customer/widget_taxvat') ?> isEnabled()): ?> setTaxvat($this->getCustomer()->getTaxvat())->toHtml() ?> + +getLayout()->createBlock('customer/widget_gender') ?> +isEnabled()): ?> + setGender($this->getCustomer()->getGender())->toHtml() ?> diff --git a/app/design/frontend/default/modern/template/customer/form/register.phtml b/app/design/frontend/default/modern/template/customer/form/register.phtml index 03d14950d6..f06344cb0f 100644 --- a/app/design/frontend/default/modern/template/customer/form/register.phtml +++ b/app/design/frontend/default/modern/template/customer/form/register.phtml @@ -65,6 +65,10 @@ getLayout()->createBlock('customer/widget_taxvat') ?> isEnabled()): ?> setTaxvat($this->getFormData()->getTaxvat())->toHtml() ?> + +getLayout()->createBlock('customer/widget_gender') ?> +isEnabled()): ?> + setGender($this->getFormData()->getGender())->toHtml() ?> @@ -152,6 +156,6 @@ \ No newline at end of file + diff --git a/app/design/frontend/default/modern/template/customer/widget/gender.phtml b/app/design/frontend/default/modern/template/customer/widget/gender.phtml new file mode 100644 index 0000000000..ab3edaf623 --- /dev/null +++ b/app/design/frontend/default/modern/template/customer/widget/gender.phtml @@ -0,0 +1,34 @@ + +isRequired()) echo ' class="required"' ?>>isRequired()) echo '*' ?>__('Gender') ?> +getFieldParams() ?>"> + getAttribute('gender')->getSource()->getAllOptions();?> + getGender();?> + + > + + \ No newline at end of file diff --git a/app/design/frontend/default/modern/template/cybersource/form.phtml b/app/design/frontend/default/modern/template/cybersource/form.phtml index 57243395bb..e50eed87d6 100644 --- a/app/design/frontend/default/modern/template/cybersource/form.phtml +++ b/app/design/frontend/default/modern/template/cybersource/form.phtml @@ -59,41 +59,52 @@ __('Expiration Date') ?> * - - getInfoData('cc_exp_month') ?> - getCcMonths() as $k=>$v): ?> - selected="selected"> - - + + + getInfoData('cc_exp_month') ?> + getCcMonths() as $k=>$v): ?> + selected="selected"> + + + getInfoData('cc_exp_year') ?> - - getCcYears() as $k=>$v): ?> - selected="selected"> - - + + + getCcYears() as $k=>$v): ?> + selected="selected"> + + + hasSsCardType()): ?> __('Switch/Solo/Maestro(UK Domestic) Only') ?> * - - __('Issue Number') ?>: - - __('Start Date') ?>: - - getInfoData('cc_ss_start_month') ?> - getCcMonths() as $k=>$v): ?> - selected="selected"> - - - - getInfoData('cc_ss_start_year') ?> - getSsStartYears() as $k=>$v): ?> - selected="selected"> - - + + + __('Issue Number') ?>: + + __('Start Date') ?>: + + + getInfoData('cc_ss_start_month') ?> + getCcMonths() as $k=>$v): ?> + selected="selected"> + + + + + + getInfoData('cc_ss_start_year') ?> + getSsStartYears() as $k=>$v): ?> + selected="selected"> + + + + + \ No newline at end of file diff --git a/app/design/frontend/default/modern/template/cybersource/info.phtml b/app/design/frontend/default/modern/template/cybersource/info.phtml index 09c61b10ff..0f53127497 100644 --- a/app/design/frontend/default/modern/template/cybersource/info.phtml +++ b/app/design/frontend/default/modern/template/cybersource/info.phtml @@ -29,7 +29,6 @@ __('Credit Card Type: %s', $this->getCcTypeName()) ?> __('Credit Card Number: xxxx-%s', $this->getInfo()->getCcLast4()) ?> -__('Expiration Date: %s/%s', $this->getCcExpMonth(), $this->getInfo()->getCcExpYear()) ?> getInfo()->getCcSsIssue()): ?> __("Switch/Solo/Maestro(UK Domestic) card issue number: %s", $this->getInfo()->getCcSsIssue()) ?> diff --git a/app/design/frontend/default/modern/template/eway/info.phtml b/app/design/frontend/default/modern/template/eway/info.phtml index 045d64a8a5..945cc5d147 100644 --- a/app/design/frontend/default/modern/template/eway/info.phtml +++ b/app/design/frontend/default/modern/template/eway/info.phtml @@ -27,8 +27,7 @@ getInfo()): ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/modern/template/flo2cash/info.phtml b/app/design/frontend/default/modern/template/flo2cash/info.phtml index b6c27bfca7..0a3b2be499 100644 --- a/app/design/frontend/default/modern/template/flo2cash/info.phtml +++ b/app/design/frontend/default/modern/template/flo2cash/info.phtml @@ -28,8 +28,7 @@ getMethod()->getTitle() ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/modern/template/giftmessage/inline.phtml b/app/design/frontend/default/modern/template/giftmessage/inline.phtml index 8612430ce7..6ff6098e26 100644 --- a/app/design/frontend/default/modern/template/giftmessage/inline.phtml +++ b/app/design/frontend/default/modern/template/giftmessage/inline.phtml @@ -32,7 +32,7 @@ if(!window.toogleVisibilityOnObjects) { if($(source) && $(source).checked) { objects.each(function(item){ $(item).show(); - $(item).getElementsBySelector('.input-text').each(function(item) { + $$('#' + item + ' .input-text').each(function(item) { item.removeClassName('validation-passed'); }); }); @@ -41,11 +41,11 @@ if(!window.toogleVisibilityOnObjects) { } else { objects.each(function(item){ $(item).hide(); - $(item).getElementsBySelector('.input-text').each(function(sitem) { + $$('#' + item + ' .input-text').each(function(sitem) { sitem.addClassName('validation-passed'); }); - $(item).getElementsBySelector('giftmessage-area').each(function(sitem) { + $$('#' + item + ' .giftmessage-area').each(function(sitem) { sitem.value = ''; }); }); diff --git a/app/design/frontend/default/modern/template/newsletter/subscribe.phtml b/app/design/frontend/default/modern/template/newsletter/subscribe.phtml index bb295cb359..eb7dec1a51 100644 --- a/app/design/frontend/default/modern/template/newsletter/subscribe.phtml +++ b/app/design/frontend/default/modern/template/newsletter/subscribe.phtml @@ -24,7 +24,7 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> - + __('Newsletter') ?> __('Newsletter Sign-up') ?>: diff --git a/app/design/frontend/default/modern/template/ogone/form.phtml b/app/design/frontend/default/modern/template/ogone/form.phtml new file mode 100644 index 0000000000..60eb0e4e58 --- /dev/null +++ b/app/design/frontend/default/modern/template/ogone/form.phtml @@ -0,0 +1,35 @@ + + + + getMethodCode() ?> + + + __('You will be redirected to Ogone website when you place an order.') ?> + + + diff --git a/app/design/frontend/default/modern/template/ogone/info.phtml b/app/design/frontend/default/modern/template/ogone/info.phtml new file mode 100644 index 0000000000..7124b5cc70 --- /dev/null +++ b/app/design/frontend/default/modern/template/ogone/info.phtml @@ -0,0 +1,27 @@ + +htmlEscape($this->getMethod()->getTitle()) ?> diff --git a/app/design/frontend/default/modern/template/ogone/paypage.phtml b/app/design/frontend/default/modern/template/ogone/paypage.phtml new file mode 100644 index 0000000000..67d0f42b0f --- /dev/null +++ b/app/design/frontend/default/modern/template/ogone/paypage.phtml @@ -0,0 +1,28 @@ + + +$$$PAYMENT ZONE$$$ diff --git a/app/design/frontend/default/modern/template/ogone/placeform.phtml b/app/design/frontend/default/modern/template/ogone/placeform.phtml new file mode 100644 index 0000000000..bf7022dc55 --- /dev/null +++ b/app/design/frontend/default/modern/template/ogone/placeform.phtml @@ -0,0 +1,42 @@ + + +__('Please, wait a moment. This page will transfere you data to ogone payment in few seconds.'); ?> + +getFormData())): ?> + getFormData() as $name => $value) { ?> + + + + + + diff --git a/app/design/frontend/default/modern/template/paybox/direct/info.phtml b/app/design/frontend/default/modern/template/paybox/direct/info.phtml index 1f257d6c67..6de67d2029 100644 --- a/app/design/frontend/default/modern/template/paybox/direct/info.phtml +++ b/app/design/frontend/default/modern/template/paybox/direct/info.phtml @@ -28,8 +28,7 @@ getMethod()->getTitle() ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/modern/template/payment/form/cc.phtml b/app/design/frontend/default/modern/template/payment/form/cc.phtml index f2f6bf5ab6..ea8ffd5947 100644 --- a/app/design/frontend/default/modern/template/payment/form/cc.phtml +++ b/app/design/frontend/default/modern/template/payment/form/cc.phtml @@ -55,20 +55,20 @@ __('Expiration Date') ?> * - - getInfoData('cc_exp_month') ?> - getCcMonths() as $k=>$v): ?> - selected="selected"> - - + + getInfoData('cc_exp_month') ?> + getCcMonths() as $k=>$v): ?> + selected="selected"> + + - - getInfoData('cc_exp_year') ?> - - getCcYears() as $k=>$v): ?> - selected="selected"> - - + + getInfoData('cc_exp_year') ?> + + getCcYears() as $k=>$v): ?> + selected="selected"> + + diff --git a/app/design/frontend/default/modern/template/payment/info/cc.phtml b/app/design/frontend/default/modern/template/payment/info/cc.phtml index 781ce84161..1353410339 100644 --- a/app/design/frontend/default/modern/template/payment/info/cc.phtml +++ b/app/design/frontend/default/modern/template/payment/info/cc.phtml @@ -27,8 +27,7 @@ getInfo()): ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/modern/template/payment/info/ccsave.phtml b/app/design/frontend/default/modern/template/payment/info/ccsave.phtml index 42244c4bbc..a619daafc5 100644 --- a/app/design/frontend/default/modern/template/payment/info/ccsave.phtml +++ b/app/design/frontend/default/modern/template/payment/info/ccsave.phtml @@ -26,5 +26,4 @@ ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> diff --git a/app/design/frontend/default/modern/template/paypaluk/direct/form.phtml b/app/design/frontend/default/modern/template/paypaluk/direct/form.phtml index d6dc8d2f50..7ccfcefb0c 100644 --- a/app/design/frontend/default/modern/template/paypaluk/direct/form.phtml +++ b/app/design/frontend/default/modern/template/paypaluk/direct/form.phtml @@ -34,25 +34,32 @@ __('Switch/Solo Only') ?> * - - __('Issue Number') ?>: - - __('Start Date') ?>: - - getCcMonths() as $k=>$v): ?> - getInfoData('cc_ss_start_month')): ?> selected="selected"> - - - - getSsStartYears() as $k=>$v): ?> - getInfoData('cc_ss_start_year')): ?> selected="selected"> - - + + + __('Issue Number') ?>: + + + + __('Start Date') ?>: + + + getCcMonths() as $k=>$v): ?> + getInfoData('cc_ss_start_month')): ?> selected="selected"> + + + + + + getSsStartYears() as $k=>$v): ?> + getInfoData('cc_ss_start_year')): ?> selected="selected"> + + + - + + + diff --git a/app/design/frontend/default/modern/template/paypaluk/direct/info.phtml b/app/design/frontend/default/modern/template/paypaluk/direct/info.phtml index 9e99138117..44eccb4078 100644 --- a/app/design/frontend/default/modern/template/paypaluk/direct/info.phtml +++ b/app/design/frontend/default/modern/template/paypaluk/direct/info.phtml @@ -28,7 +28,6 @@ __('Credit Card Type: %s', $this->getCcTypeName()) ?> __('Credit Card Number: xxxx-%s', $this->getInfo()->getCcLast4()) ?> -__('Expiration Date: %s/%s', $this->getCcExpMonth(), $this->getInfo()->getCcExpYear()) ?> getInfo()->getCcSsIssue()): ?> __("Switch/Solo card issue number: %s", $this->getInfo()->getCcSsIssue()) ?> diff --git a/app/design/frontend/default/modern/template/reports/product/widget/compared.phtml b/app/design/frontend/default/modern/template/reports/product/widget/compared.phtml new file mode 100644 index 0000000000..814dba0396 --- /dev/null +++ b/app/design/frontend/default/modern/template/reports/product/widget/compared.phtml @@ -0,0 +1,67 @@ + + +getRecentlyComparedProducts()): ?> + + __('Recently Compared Products') ?> + + getItems() as $_product): ?> + 5): continue; endif; ?> + + + + + + htmlEscape($_product->getName()) ?> + getReviewsSummaryHtml($_product, 'short') ?> + getPriceHtml($_product, true, '-new') ?> + + isSaleable()): ?> + __('Add to Cart') ?> + + getIsInStock()): ?> + __('In stock') ?> + + __('Out of stock') ?> + + + + helper('wishlist')->isAllow()) : ?> + __('Add to Wishlist') ?> + + + + + + + + + diff --git a/app/design/frontend/default/modern/template/reports/product/widget/viewed.phtml b/app/design/frontend/default/modern/template/reports/product/widget/viewed.phtml new file mode 100644 index 0000000000..3da1958b9f --- /dev/null +++ b/app/design/frontend/default/modern/template/reports/product/widget/viewed.phtml @@ -0,0 +1,70 @@ + + +getRecentlyViewedProducts()): ?> + + __('Recently Viewed Products') ?> + + getItems() as $_product): ?> + 5): continue; endif; ?> + + + + + + htmlEscape($_product->getName()) ?> + getReviewsSummaryHtml($_product, 'short') ?> + getPriceHtml($_product, true, '-new') ?> + + isSaleable()): ?> + __('Add to Cart') ?> + + getIsInStock()): ?> + __('In stock') ?> + + __('Out of stock') ?> + + + + helper('wishlist')->isAllow()) : ?> + __('Add to Wishlist') ?> + + getAddToCompareUrl($_product)): ?> + | __('Add to Compare') ?> + + + + + + + + + diff --git a/app/design/frontend/default/modern/template/sales/order/shipment/items.phtml b/app/design/frontend/default/modern/template/sales/order/shipment/items.phtml index 79b64d4f26..7d57b01f32 100644 --- a/app/design/frontend/default/modern/template/sales/order/shipment/items.phtml +++ b/app/design/frontend/default/modern/template/sales/order/shipment/items.phtml @@ -27,7 +27,7 @@ getOrder() ?> getTracksCollection()->count()) : ?> - __('Track all shipments') ?> | + __('Track all shipments') ?> | __('Print All Shipments') ?> @@ -41,7 +41,7 @@ - __('Track this shipment') ?> + __('Track this shipment') ?> @@ -54,7 +54,7 @@ isCustom()): ?> getNumber() ?> - getNumber() ?> + getNumber() ?> , getTracksCollection()->count()) : ?> | -__('Track your order') ?> +__('Track your order') ?> diff --git a/app/design/frontend/default/modern/template/sales/reorder/sidebar.phtml b/app/design/frontend/default/modern/template/sales/reorder/sidebar.phtml index faf73d3790..ee3dba7514 100644 --- a/app/design/frontend/default/modern/template/sales/reorder/sidebar.phtml +++ b/app/design/frontend/default/modern/template/sales/reorder/sidebar.phtml @@ -33,7 +33,7 @@ ?> getLastOrder()): ?> - + __('My Orders') ?> __('View All'); ?> diff --git a/app/design/frontend/default/modern/template/tag/customer/view.phtml b/app/design/frontend/default/modern/template/tag/customer/view.phtml index 40730ea17f..7807f04d22 100644 --- a/app/design/frontend/default/modern/template/tag/customer/view.phtml +++ b/app/design/frontend/default/modern/template/tag/customer/view.phtml @@ -31,9 +31,7 @@ getMessagesBlock()->getGroupedHtml() ?> __('Tag Name: %s', $this->htmlEscape($this->getTagInfo()->getName())) ?> - ( __('Edit Tag') ?> - | - __('Delete Tag') ?> ) + __('Delete') ?> getToolbarHtml() ?> diff --git a/app/design/frontend/default/modern/template/wishlist/view.phtml b/app/design/frontend/default/modern/template/wishlist/view.phtml index 8032fad6f5..10ebf521ce 100644 --- a/app/design/frontend/default/modern/template/wishlist/view.phtml +++ b/app/design/frontend/default/modern/template/wishlist/view.phtml @@ -80,7 +80,8 @@ « __('Back') ?> - + + isSaleable()):?> diff --git a/app/etc/config.xml b/app/etc/config.xml index 3de0ce3c6e..c1bce6328e 100644 --- a/app/etc/config.xml +++ b/app/etc/config.xml @@ -53,6 +53,7 @@ + @@ -391,5 +392,11 @@ {{var_dir}}/export + + + en_US + America/Los_Angeles + + \ No newline at end of file diff --git a/app/etc/modules/Mage_All.xml b/app/etc/modules/Mage_All.xml index 1fb9173d56..7d9fcafc56 100644 --- a/app/etc/modules/Mage_All.xml +++ b/app/etc/modules/Mage_All.xml @@ -108,6 +108,8 @@ + + @@ -388,5 +390,12 @@ + + true + core + + + + diff --git a/app/etc/modules/Mage_Ogone.xml b/app/etc/modules/Mage_Ogone.xml new file mode 100644 index 0000000000..b74f4c91d5 --- /dev/null +++ b/app/etc/modules/Mage_Ogone.xml @@ -0,0 +1,39 @@ + + + + + + true + core + + + + + + + diff --git a/index.php b/index.php index f448bd8ab6..d335026c86 100644 --- a/index.php +++ b/index.php @@ -57,9 +57,15 @@ #Varien_Profiler::enable(); -#Mage::setIsDeveloperMode(true); +if (isset($_SERVER['MAGE_IS_DEVELOPER_MODE'])) { + Mage::setIsDeveloperMode(true); +} #ini_set('display_errors', 1); umask(0); -Mage::run(); + +$mageRunCode = isset($_SERVER['MAGE_RUN_CODE']) ? $_SERVER['MAGE_RUN_CODE'] : ''; +$mageRunType = isset($_SERVER['MAGE_RUN_TYPE']) ? $_SERVER['MAGE_RUN_TYPE'] : 'store'; + +Mage::run($mageRunCode, $mageRunType); diff --git a/js/mage/adminhtml/form.js b/js/mage/adminhtml/form.js index afa8cad7e8..d007fa9897 100644 --- a/js/mage/adminhtml/form.js +++ b/js/mage/adminhtml/form.js @@ -42,6 +42,9 @@ varienForm.prototype = { }, submit : function(url){ + if (typeof varienGlobalEvents != undefined) { + varienGlobalEvents.fireEvent('formSubmit', this.formId); + } this.errorSections = $H({}); this.canShowError = true; this.submitUrl = url; @@ -275,6 +278,7 @@ RegionUpdater.prototype = { // Element.remove(this.regionSelectEl); // this.regionSelectEl = null; } + varienGlobalEvents.fireEvent("address_country_changed", this.countryEl); }, setMarkDisplay: function(elem, display){ diff --git a/js/mage/adminhtml/grid.js b/js/mage/adminhtml/grid.js index ebccf2f5f9..70f079f435 100644 --- a/js/mage/adminhtml/grid.js +++ b/js/mage/adminhtml/grid.js @@ -65,16 +65,6 @@ varienGrid.prototype = { Event.observe(this.rows[row],'mouseout',this.trOnMouseOut); Event.observe(this.rows[row],'click',this.trOnClick); Event.observe(this.rows[row],'dblclick',this.trOnDblClick); - - if(this.initRowCallback){ - try { - this.initRowCallback(this, this.rows[row]); - } catch (e) { - if(console) { - console.log(e); - } - } - } } } if(this.sortVar && this.dirVar){ @@ -97,6 +87,19 @@ varienGrid.prototype = { } } }, + initGridRows: function() { + if(this.initRowCallback){ + for (var row=0; row 0; + + //Hash with grid data + this.gridData = this.getGridDataHash(predefinedData); + + //Hidden input data holder + this.hiddenDataHolder = $(hiddenDataHolder); + this.hiddenDataHolder.value = this.serializeObject(); + + this.grid = grid; + + // Set old callbacks + this.setOldCallback('row_click', this.grid.rowClickCallback); + this.setOldCallback('init_row', this.grid.initRowCallback); + this.setOldCallback('checkbox_check', this.grid.checkboxCheckCallback); + + //Grid + this.reloadParamName = reloadParamName; + this.grid.reloadParams = {}; + this.grid.reloadParams[this.reloadParamName+'[]'] = this.getDataForReloadParam(); + this.grid.rowClickCallback = this.rowClick.bind(this); + this.grid.initRowCallback = this.rowInit.bind(this); + this.grid.checkboxCheckCallback = this.registerData.bind(this); + this.grid.rows.each(this.eachRow.bind(this)); + }, + setOldCallback: function (callbackName, callback) { + this.oldCallbacks[callbackName] = callback; + }, + getOldCallback: function (callbackName) { + return this.oldCallbacks[callbackName] ? this.oldCallbacks[callbackName] : Prototype.emptyFunction; + }, + registerData : function(grid, element, checked) { + if(this.multidimensionalMode){ + if(checked){ + if(element.inputElements) { + this.gridData.set(element.value, {}); + for(var i = 0; i < element.inputElements.length; i++) { + element.inputElements[i].disabled = false; + this.gridData.get(element.value)[element.inputElements[i].name] = element.inputElements[i].value; + } + } + } + else{ + if(element.inputElements){ + for(var i = 0; i < element.inputElements.length; i++) { + element.inputElements[i].disabled = true; + } + } + this.gridData.unset(element.value); + } + } + else{ + if(checked){ + this.gridData.set(element.value, element.value); + } + else{ + this.gridData.unset(element.value); + } + } + + this.hiddenDataHolder.value = this.serializeObject(); + this.grid.reloadParams = {}; + this.grid.reloadParams[this.reloadParamName+'[]'] = this.getDataForReloadParam(); + this.getOldCallback('checkbox_check')(grid, element, checked); + }, + eachRow : function(row) { + this.rowInit(this.grid, row); + }, + rowClick : function(grid, event) { + var trElement = Event.findElement(event, 'tr'); + var isInput = Event.element(event).tagName == 'INPUT'; + if(trElement){ + var checkbox = Element.select(trElement, 'input'); + if(checkbox[0]){ + var checked = isInput ? checkbox[0].checked : !checkbox[0].checked; + this.grid.setCheckboxChecked(checkbox[0], checked); + } + } + this.getOldCallback('row_click')(grid, event); + }, + inputChange : function(event) { + var element = Event.element(event); + if(element && element.checkboxElement && element.checkboxElement.checked){ + this.gridData.get(element.checkboxElement.value)[element.name] = element.value; + this.hiddenDataHolder.value = this.serializeObject(); + } + }, + 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); + if(checkbox && inputs.length > 0) { + checkbox.inputElements = inputs; + for(var i = 0; i < inputs.length; i++) { + inputs[i].checkboxElement = checkbox; + if(this.gridData.get(checkbox.value) && this.gridData.get(checkbox.value)[inputs[i].name]) { + inputs[i].value = this.gridData.get(checkbox.value)[inputs[i].name]; + } + inputs[i].disabled = !checkbox.checked; + inputs[i].tabIndex = this.tabIndex++; + Event.observe(inputs[i],'keyup', this.inputChange.bind(this)); + Event.observe(inputs[i],'change', this.inputChange.bind(this)); + } + } + } + this.getOldCallback('init_row')(grid, row); + }, + + //Stuff methods + getGridDataHash: function (_object){ + return $H(this.multidimensionalMode ? _object : this.convertArrayToObject(_object)) + }, + getDataForReloadParam: function(){ + return this.multidimensionalMode ? this.gridData.keys() : this.gridData.values(); + }, + serializeObject: function(){ + if(this.multidimensionalMode){ + var clone = this.gridData.clone(); + clone.each(function(pair) { + clone.set(pair.key, encode_base64(Object.toQueryString(pair.value))); + }); + return clone.toQueryString(); + } + else{ + return this.gridData.values().join('&'); + } + }, + convertArrayToObject: function (_array){ + var _object = {}; + for(var i = 0, l = _array.length; i < l; i++){ + _object[_array[i]] = _array[i]; + } + return _object; + } +}; diff --git a/js/mage/adminhtml/product.js b/js/mage/adminhtml/product.js index dcb935e418..587abadd49 100644 --- a/js/mage/adminhtml/product.js +++ b/js/mage/adminhtml/product.js @@ -333,12 +333,23 @@ Product.Configurable.prototype = { //var li = Builder.node('li', {className:'attribute'}); var li = $(document.createElement('LI')); li.className = 'attribute'; + li.id = this.idPrefix + '_attribute_' + index; attribute.html_id = li.id; if(attribute && attribute.label && attribute.label.blank()) { attribute.label = ' ' } - li.update(this.addAttributeTemplate.evaluate(attribute)); + var label_readonly = ''; + var use_default_checked = ''; + if (attribute.use_default == '1') { + use_default_checked = ' checked="checked"'; + label_readonly = ' redonly="redonly"'; + } + + var template = this.addAttributeTemplate.evaluate(attribute); + template = template.replace(new RegExp(' readonly="label"', 'g'), label_readonly); + template = template.replace(new RegExp(' checked="use_default"', 'g'), use_default_checked); + li.update(template); li.attributeObject = attribute; this.container.appendChild(li); @@ -353,6 +364,7 @@ Product.Configurable.prototype = { /* Observe label change */ Event.observe(li.down('.attribute-label'),'change', this.onLabelUpdate); Event.observe(li.down('.attribute-label'),'keyup', this.onLabelUpdate); + Event.observe(li.down('.attribute-use-default-label'), 'change', this.onLabelUpdate); }.bind(this)); if (!this.readonly) { // Creation of sortable for attributes sorting @@ -363,7 +375,18 @@ Product.Configurable.prototype = { updateLabel: function (event) { var li = Event.findElement(event, 'LI'); - li.attributeObject.label = Event.element(event).value; + var labelEl = li.down('.attribute-label'); + var defEl = li.down('.attribute-use-default-label'); + + li.attributeObject.label = labelEl.value; + if (defEl.checked) { + labelEl.readOnly = true; + li.attributeObject.use_default = 1; + } else { + labelEl.readOnly = false; + li.attributeObject.use_default = 0; + } + this.updateSaveInput(); }, updatePositions: function(param) { @@ -550,7 +573,7 @@ Product.Configurable.prototype = { this.valueAutoIndex = 1; } templateVariables.set('html_id', container.id + '_' + this.valueAutoIndex); - templateVariables = templateVariables.merge(value); + templateVariables.update(value); var pricingValue = parseFloat(templateVariables.get('pricing_value')); if (!isNaN(pricingValue)) { templateVariables.set('pricing_value', pricingValue); diff --git a/js/mage/adminhtml/rules.js b/js/mage/adminhtml/rules.js index 624e4c50f0..6a71ee9325 100644 --- a/js/mage/adminhtml/rules.js +++ b/js/mage/adminhtml/rules.js @@ -38,7 +38,6 @@ VarienRulesForm.prototype = { initParam: function (container) { container.rulesObject = this; - var label = Element.down(container, '.label'); if (label) { Event.observe(label, 'click', this.showParamInputField.bind(this, container)); @@ -55,7 +54,8 @@ VarienRulesForm.prototype = { if (apply) { Event.observe(apply, 'click', this.hideParamInputField.bind(this, container)); } else { - elem = elem.down(); + elem = elem.down('.element-value-changer'); + elem.container = container; if (!elem.multiple) { Event.observe(elem, 'change', this.hideParamInputField.bind(this, container)); } @@ -71,11 +71,15 @@ VarienRulesForm.prototype = { showChooserElement: function (chooser) { this.chooserSelectedItems = $H({}); - var values = this.updateElement.value.split(','), s=''; - for (i=0; i> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + output = output + + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); + } + + return output; + }, + + // public method for decoding + decode: function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + while (i < input.length) { + + enc1 = this._keyStr.indexOf(input.charAt(i++)); + enc2 = this._keyStr.indexOf(input.charAt(i++)); + enc3 = this._keyStr.indexOf(input.charAt(i++)); + enc4 = this._keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + } + output = Base64._utf8_decode(output); + return output; + }, + + mageEncode: function(input){ + return this.encode(input).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ','); + }, + + mageDecode: function(output){ + output = output.replace(/\-/g, '+').replace(/_/g, '/').replace(/,/g, '='); + return this.decode(output); + }, + + idEncode: function(input){ + return this.encode(input).replace(/\+/g, ':').replace(/\//g, '_').replace(/=/g, '-'); + }, + + idDecode: function(output){ + output = output.replace(/\-/g, '=').replace(/_/g, '/').replace(/\:/g, '\+'); + return this.decode(output); + }, + + // private method for UTF-8 encoding + _utf8_encode : function (string) { + string = string.replace(/\r\n/g,"\n"); + var utftext = ""; + + for (var n = 0; n < string.length; n++) { + + var c = string.charCodeAt(n); + + if (c < 128) { + utftext += String.fromCharCode(c); + } + else if((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } + else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + } + return utftext; + }, + + // private method for UTF-8 decoding + _utf8_decode : function (utftext) { + var string = ""; + var i = 0; + var c = c1 = c2 = 0; + + while ( i < utftext.length ) { + + c = utftext.charCodeAt(i); + + if (c < 128) { + string += String.fromCharCode(c); + i++; + } + else if((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i+1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } + else { + c2 = utftext.charCodeAt(i+1); + c3 = utftext.charCodeAt(i+2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + return string; + } +}; 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 new file mode 100644 index 0000000000..32a2983c1e --- /dev/null +++ b/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js @@ -0,0 +1,90 @@ +/** + * 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. + * + * @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) + */ +tinyMCE.addI18n({en:{ + magentowidget: + { + insert_widget : "Insert Widget" + } +}}); + +/* + TODO: Apply JStrim to reduce file size +*/ + +(function() { + tinymce.create('tinymce.plugins.MagentowidgetPlugin', { + /** + * @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) { + ed.addCommand('mceMagentowidget', function() { + ed.windowManager.open({ + file : ed.settings.magentowidget_url, + width : 1024, + height : 800, + popup_css : ed.settings.custom_popup_css, + scrollbars : 'yes' + }, { + plugin_url : url + }); + }); + + // Register Widget plugin button + ed.addButton('magentowidget', { + title : 'magentowidget.insert_widget', + cmd : 'mceMagentowidget', + image : url + '/img/icon.gif' + }); + + // Add a node change handler, selects the button in the UI when a image is selected + ed.onNodeChange.add(function(ed, cm, n) { + if (n.className == 'widget' && n.nodeName == 'IMG') { + cm.setActive('magentowidget', true); + } else { + cm.setActive('magentowidget', false); + } + }); + + // Add a widget placeholder image double click callback + ed.onDblClick.add(function(ed, e) { + if (e.target.className == 'widget' && e.target.nodeName == 'IMG') { + ed.execCommand('mceMagentowidget'); + } + }); + }, + + getInfo : function() { + return { + longname : 'Magento Widget Manager Plugin for TinyMCE 3.x', + author : 'Magento Core Team', + authorurl : 'http://magentocommerce.com', + infourl : 'http://magentocommerce.com', + version : "1.0" + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('magentowidget', tinymce.plugins.MagentowidgetPlugin); +})(); diff --git a/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin_src.js b/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin_src.js new file mode 100644 index 0000000000..32a2983c1e --- /dev/null +++ b/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin_src.js @@ -0,0 +1,90 @@ +/** + * 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. + * + * @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) + */ +tinyMCE.addI18n({en:{ + magentowidget: + { + insert_widget : "Insert Widget" + } +}}); + +/* + TODO: Apply JStrim to reduce file size +*/ + +(function() { + tinymce.create('tinymce.plugins.MagentowidgetPlugin', { + /** + * @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) { + ed.addCommand('mceMagentowidget', function() { + ed.windowManager.open({ + file : ed.settings.magentowidget_url, + width : 1024, + height : 800, + popup_css : ed.settings.custom_popup_css, + scrollbars : 'yes' + }, { + plugin_url : url + }); + }); + + // Register Widget plugin button + ed.addButton('magentowidget', { + title : 'magentowidget.insert_widget', + cmd : 'mceMagentowidget', + image : url + '/img/icon.gif' + }); + + // Add a node change handler, selects the button in the UI when a image is selected + ed.onNodeChange.add(function(ed, cm, n) { + if (n.className == 'widget' && n.nodeName == 'IMG') { + cm.setActive('magentowidget', true); + } else { + cm.setActive('magentowidget', false); + } + }); + + // Add a widget placeholder image double click callback + ed.onDblClick.add(function(ed, e) { + if (e.target.className == 'widget' && e.target.nodeName == 'IMG') { + ed.execCommand('mceMagentowidget'); + } + }); + }, + + getInfo : function() { + return { + longname : 'Magento Widget Manager Plugin for TinyMCE 3.x', + author : 'Magento Core Team', + authorurl : 'http://magentocommerce.com', + infourl : 'http://magentocommerce.com', + version : "1.0" + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('magentowidget', tinymce.plugins.MagentowidgetPlugin); +})(); diff --git a/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/img/icon.gif b/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/img/icon.gif new file mode 100644 index 0000000000000000000000000000000000000000..d1618a007e3f8e9fa80e34268c9e0c0fa769b3eb GIT binary patch literal 572 zcmZ?wbhEHb6krfwc*el+;lsNhagv|+EdJKu{j<^M!}Eti{kEv) zCDa#ub+Q9y!P|HrC%qf zf2(uEp{MA6`EBk*fT8_kyqM`+qigeLl46M_b6Jv%7y**#GEH{5r4fN1DplNhv>T-M<`K z{Ubs8^WG(2x6b%JA?ati-SsZXOJ~#WW8{df$=!oeTECS?p%6!{K@T$_Z~cY_3Z8C u_l!^97)pJ9`IS}DjJ4}ftH3e;NelQbm@G0HnVJu}h{>$TP|y@$um%8m8Nze` literal 0 HcmV?d00001 diff --git a/js/mage/adminhtml/wysiwyg/tiny_mce/setup.js b/js/mage/adminhtml/wysiwyg/tiny_mce/setup.js new file mode 100644 index 0000000000..eab013858f --- /dev/null +++ b/js/mage/adminhtml/wysiwyg/tiny_mce/setup.js @@ -0,0 +1,222 @@ +/** + * 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. + * + * @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) + */ + +var tinyMceWysiwygSetup = Class.create(); +tinyMceWysiwygSetup.prototype = +{ + initialize: function(htmlId, config) + { + this.id = htmlId; + this.config = config; + varienGlobalEvents.attachEventHandler('tinymceChange', this.onChangeContent.bind(this)); + }, + + setup: function() + { + if (this.config.widget_plugin_src) { + tinymce.PluginManager.load('magentowidget', this.config.widget_plugin_src); + } + tinyMCE.init(this.getSettings()); + }, + + getSettings: function() + { + var plugins = 'safari,pagebreak,style,layer,table,advhr,advimage,emotions,iespell,media,searchreplace,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras'; + + if (this.config.widget_plugin_src) { + plugins = '-magentowidget,' + plugins; + } + + var settings = { + mode : 'exact', + elements : this.id, + theme : 'advanced', + plugins : plugins, + theme_advanced_buttons1 : 'magentowidget,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect', + theme_advanced_buttons2 : 'cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,forecolor,backcolor', + theme_advanced_buttons3 : 'tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,iespell,media,advhr,|,ltr,rtl,|,fullscreen', + theme_advanced_buttons4 : 'insertlayer,moveforward,movebackward,absolute,|,styleprops,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,pagebreak', + theme_advanced_toolbar_location : 'top', + theme_advanced_toolbar_align : 'left', + theme_advanced_statusbar_location : 'bottom', + theme_advanced_resizing : true, + convert_urls : false, + relative_urls : false, + content_css: this.config.content_css, + custom_popup_css: this.config.popup_css, + magentowidget_url: this.config.widget_window_url, + doctype : '', + + setup : function(ed) { + ed.onSubmit.add(function(ed, e) { + varienGlobalEvents.fireEvent('tinymceSubmit', e); + }); + + ed.onPaste.add(function(ed, e, o) { + varienGlobalEvents.fireEvent('tinymcePaste', o); + }); + + ed.onBeforeSetContent.add(function(ed, o) { + varienGlobalEvents.fireEvent('tinymceBeforeSetContent', o); + }); + + ed.onSetContent.add(function(ed, o) { + varienGlobalEvents.fireEvent('tinymceSetContent', o); + }); + + ed.onSaveContent.add(function(ed, o) { + varienGlobalEvents.fireEvent('tinymceSaveContent', o); + }); + + ed.onChange.add(function(ed, l) { + varienGlobalEvents.fireEvent('tinymceChange', l); + }); + + ed.onExecCommand.add(function(ed, cmd, ui, val) { + varienGlobalEvents.fireEvent('tinymceExecCommand', cmd); + }); + } + }; + + if (this.config.files_browser_window_url) { + settings.file_browser_callback = 'imagebrowser'; + } + + return settings; + }, + + openImagesBrowser: function(o) { + var win = o.win; + var type = o.type; + var field = o.field; + var wWidth = this.config.files_browser_window_width; + var wHeight = this.config.files_browser_window_height; + var wUrl = this.config.files_browser_window_url; + if (type != undefined && type != "") { + wUrl = wUrl + "type/" + type + "/"; + } + win.open(wUrl, "imagesBrowser", "width=" + wWidth + ", height=" + wHeight); + }, + + toggle: function() { + this.toggleEditorControl(); + + $$('#buttons' + this.id + ' > button.plugin').each(function(e) { + e.toggle(); + }); + }, + + toggleEditorControl: function() { + if (!tinyMCE.get(this.id)) { + this.setup(); + setTimeout('',1000); + tinyMCE.execCommand('mceAddControl', false, this.id); + return true; + } else { + tinyMCE.execCommand('mceRemoveControl', false, this.id); + return false; + } + }, + + onFormValidation: function() { + if (tinyMCE.get(this.id)) { + $(this.id).value = tinyMCE.get(this.id).getContent(); + } + }, + + onChangeContent: function() { + // Add "changed" to tab class if it exists + if(this.config.tab_id) { + var tab = $$('a[id$=' + this.config.tab_id + ']')[0]; + if ($(tab) != undefined && $(tab).hasClassName('tab-item-link')) { + $(tab).addClassName('changed'); + } + } + }, + + encodeDirectives: function(content) { + return content.gsub(/(src|href)\s*\=\s*[\"\']{1}(\{\{[a-z]{0,10}.*?\}\})[\"\']{1}/i, function(match){ + return match[1] + '="' + this.config.directives_url + 'directive/' + Base64.mageEncode(match[2]) + '/"'; + }.bind(this)); + }, + + encodeWidgets: function(content) { + return content.gsub(/\{\{widget(.*?)\}\}/i, function(match){ + var attributes = this.parseAttributesString(match[1]); + var placeholderFilename = attributes.type.replace(/\//g, "__") + ".gif"; + if(!this.widgetPlaceholderExist(placeholderFilename)) { + placeholderFilename = 'default.gif'; + } + var imageSrc = this.config.widget_images_url + placeholderFilename; + + var imageHtml = ''; + + return imageHtml; + + }.bind(this)); + }, + + decodeDirectives: function(content) { + var reg = new RegExp(this.config.directives_url_quoted + 'directive\/([a-zA-Z0-9\-\_\,]+)\/?', 'i'); + return content.gsub(reg, function(match){ + return Base64.mageDecode(match[1]); + }.bind(this)); + }, + + decodeWidgets: function(content) { + return content.gsub(/]+class=\"widget\"[^>]*)>/i, function(match) { + var attributes = this.parseAttributesString(match[1]); + if(attributes.id) { + return Base64.idDecode(attributes.id); + } + return match[0]; + }.bind(this)); + }, + + parseAttributesString: function(attributes) { + var result = {}; + attributes.gsub(/(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/, function(match){ + result[match[1]] = match[2]; + }); + return result; + }, + + beforeSetContent: function(o) { + o.content = this.encodeWidgets(o.content); + o.content = this.encodeDirectives(o.content); + }, + + saveContent: function(o) { + o.content = this.decodeWidgets(o.content); + o.content = this.decodeDirectives(o.content); + }, + + widgetPlaceholderExist: function(filename) { + 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 new file mode 100644 index 0000000000..e7fc9a2d8c --- /dev/null +++ b/js/mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/content.css @@ -0,0 +1,32 @@ +body, td, pre { color:#2f2f2f; font-family:Arial, Helvetica, sans-serif; font-size:12px; margin:8px; } +body { background:#fff; } +body.mceForceColors { background:#fff; color:#2f2f2f; } +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 {width:12px; line-height:6px; overflow:hidden; padding-left:12px; background:url(img/items.gif) no-repeat bottom left;} +img.mceItemAnchor {width:12px; height:12px; background:url(img/items.gif) no-repeat;} +img {border:0; vertical-align:middle; } +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, html\: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; +} 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 new file mode 100644 index 0000000000..30f850504e --- /dev/null +++ b/js/mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/dialog.css @@ -0,0 +1,62 @@ +/* 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(img/tabs.gif) repeat-x 0 -72px;} +.tabs ul {margin:0; padding:0; list-style:none;} +.tabs li {float:left; background:url(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(img/tabs.gif) no-repeat 0 -18px; margin-right:2px;} +.tabs span {float:left; display:block; background:url(img/tabs.gif) no-repeat right -36px; padding:0px 10px 0 0;} +.tabs .current span {background:url(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;} + +/* 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;} \ No newline at end of file diff --git a/js/mage/adminhtml/wysiwyg/widget.js b/js/mage/adminhtml/wysiwyg/widget.js new file mode 100644 index 0000000000..e1a476595f --- /dev/null +++ b/js/mage/adminhtml/wysiwyg/widget.js @@ -0,0 +1,334 @@ +/** + * 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. + * + * @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) + */ + +var widgetTools = { + getDivHtml: function(id, html) { + if (!html) html = ''; + return '' + html + ''; + }, + + onAjaxSuccess: function(transport) { + if (transport.responseText.isJSON()) { + var response = transport.responseText.evalJSON() + if (response.error) { + throw response; + } else if (response.ajaxExpired && response.ajaxRedirect) { + setLocation(response.ajaxRedirect); + } + } + } +} + +var WysiwygWidget = {}; +WysiwygWidget.Widget = Class.create(); +WysiwygWidget.Widget.prototype = { + + initialize: function(formEl, widgetEl, widgetOptionsEl, optionsSourceUrl) { + $(formEl).insert({bottom: widgetTools.getDivHtml(widgetOptionsEl)}); + this.widgetEl = $(widgetEl); + this.widgetOptionsEl = $(widgetOptionsEl); + this.optionsUrl = optionsSourceUrl; + this.optionValues = new Hash({}); + + Event.observe(this.widgetEl, "change", this.loadOptions.bind(this)); + + this.initOptionValues(); + }, + + getOptionsContainerId: function() { + return this.widgetOptionsEl.id + Base64.idEncode(this.widgetEl.value); + }, + + switchOptionsContainer: function(containerId) { + $$('#' + this.widgetOptionsEl.id + ' div[id^=' + this.widgetOptionsEl.id + ']').each(function(e) { + this.disableOptionsContainer(e.id); + }.bind(this)); + if(containerId != undefined) { + this.enableOptionsContainer(containerId); + } + this._showWidgetDescription(); + }, + + enableOptionsContainer: function(containerId) { + $$('#' + containerId + ' .widget-option').each(function(e) { + e.removeClassName('skip-submit'); + if (e.hasClassName('obligatory')) { + e.removeClassName('obligatory'); + e.addClassName('required-entry'); + } + }); + $(containerId).removeClassName('no-display'); + }, + + disableOptionsContainer: function(containerId) { + if ($(containerId).hasClassName('no-display')) { + return; + } + $$('#' + containerId + ' .widget-option').each(function(e) { + // Avoid submitting fields of unactive container + if (!e.hasClassName('skip-submit')) { + e.addClassName('skip-submit'); + } + // Form validation workaround for unactive container + if (e.hasClassName('required-entry')) { + e.removeClassName('required-entry'); + e.addClassName('obligatory'); + } + }); + $(containerId).addClassName('no-display'); + }, + + // Assign widget options values when existing widget selected in WYSIWYG + initOptionValues: function() { + + if (!this.wysiwygExists()) { + return false; + } + + var e = this.getWysiwygNode(); + if (e != undefined && e.id) { + var widgetCode = Base64.idDecode(e.id); + this.optionValues = new Hash({}); + widgetCode.gsub(/([a-z0-9\_]+)\s*\=\s*[\"]{1}([^\"]+)[\"]{1}/i, function(match){ + if (match[1] == 'type') { + this.widgetEl.value = match[2]; + } else { + this.optionValues.set(match[1], match[2]); + } + }.bind(this)); + + this.loadOptions(); + } + }, + + loadOptions: function() { + if (!this.widgetEl.value) { + this.switchOptionsContainer(); + return; + } + + if ($(this.getOptionsContainerId()) != undefined) { + this.switchOptionsContainer(this.getOptionsContainerId()); + return; + } + + this._showWidgetDescription(); + + var params = {widget_type: this.widgetEl.value, values: this.optionValues}; + new Ajax.Request(this.optionsUrl, + { + parameters: {widget: Object.toJSON(params)}, + onSuccess: function(transport) { + try { + widgetTools.onAjaxSuccess(transport); + this.switchOptionsContainer(); + this.widgetOptionsEl.insert({bottom: widgetTools.getDivHtml(this.getOptionsContainerId(), transport.responseText)}); + } catch(e) { + alert(e.message); + } + }.bind(this) + } + ); + }, + + _showWidgetDescription: function() { + var noteCnt = this.widgetEl.up().next().down('small'); + var descrCnt = $('widget-description-' + this.widgetEl.selectedIndex); + if(noteCnt != undefined) { + var description = (descrCnt != undefined ? descrCnt.innerHTML : ''); + noteCnt.update(descrCnt.innerHTML); + } + }, + + insertWidget: function() { + if(editForm.validator && editForm.validator.validate() || !editForm.validator){ + var formElements = []; + var i = 0; + $(editForm.formId).getElements().each(function(e) { + if(!e.hasClassName('skip-submit')) { + formElements[i] = e; + i++; + } + }); + + // Add as_is flag to parameters if wysiwyg editor doesn't exist + var params = Form.serializeElements(formElements); + if (!this.wysiwygExists()) { + params = params + '&as_is=1'; + } + + new Ajax.Request($(editForm.formId).readAttribute("action"), + { + parameters: params, + onComplete: function(transport) { + try { + widgetTools.onAjaxSuccess(transport); + this.updateContent(transport.responseText); + this.getPopup().close(); + } catch(e) { + alert(e.message); + } + }.bind(this) + }); + } + }, + + updateContent: function(content) { + if (this.wysiwygExists()) { + this.getPopup().execCommand("mceInsertContent", false, content); + // Refocus in window + if (this.getPopup().isWindow) { + window.focus(); + } + this.getWysiwyg().focus(); + } else { + var parent = this.getPopup().opener; + var textarea = parent.document.getElementById(this.getPopup().name); + updateElementAtCursor(textarea, content, this.getPopup().opener); + } + }, + + wysiwygExists: function() { + return (typeof tinyMCEPopup != 'undefined') && (typeof tinyMCEPopup.editor != 'undefined'); + }, + + getPopup: function() { + if (this.wysiwygExists()) { + return tinyMCEPopup; + } else { + return window.self; + } + }, + + getWysiwyg: function() { + return tinyMCEPopup.editor; + }, + + getWysiwygNode: function() { + return tinyMCEPopup.editor.selection.getNode(); + } +} + +WysiwygWidget.chooser = Class.create(); +WysiwygWidget.chooser.prototype = { + + // HTML element A, on which click event fired when choose a selection + chooserId: null, + + // Source URL for Ajax requests + chooserUrl: null, + + // Chooser config + config: null, + + initialize: function(chooserId, chooserUrl, config) { + this.chooserId = chooserId; + this.chooserUrl = chooserUrl; + this.config = config; + }, + + getResponseContainerId: function() { + return 'responseCnt' + this.chooserId; + }, + + getChooserControl: function() { + return $(this.chooserId + 'control'); + }, + + getElement: function() { + return $(this.chooserId + 'value'); + }, + + getElementLabel: function() { + return $(this.chooserId + 'label'); + }, + + makeControlOpened: function() { + this.toggleControl(true); + }, + + makeControlClosed: function() { + this.toggleControl(false); + }, + + toggleControl: function(opened) { + this.getChooserControl().down('span').innerHTML = (opened ? this.config.buttons.close : this.config.buttons.open); + if(opened) { + this.getChooserControl().addClassName('opened'); + } else { + this.getChooserControl().removeClassName('opened'); + } + }, + + open: function() { + this.makeControlOpened(); + $(this.getResponseContainerId()).show(); + }, + + close: function() { + this.makeControlClosed(); + $(this.getResponseContainerId()).hide(); + }, + + choose: function(event) { + + // Show or hide chooser content if it was already loaded + var responseContainerId = this.getResponseContainerId(); + if ($(responseContainerId) != undefined) { + $(responseContainerId).visible() ? this.close() : this.open(); + return; + } + + // Otherwise load content from server + new Ajax.Request(this.chooserUrl, + { + parameters: {element_value: this.getElementValue(), element_label: this.getElementLabelText()}, + onSuccess: function(transport) { + try { + widgetTools.onAjaxSuccess(transport); + this.getChooserControl().insert({after: widgetTools.getDivHtml(responseContainerId, transport.responseText)}); + this.makeControlOpened(); + } catch(e) { + alert(e.message); + } + }.bind(this) + } + ); + }, + + getElementValue: function(value) { + return this.getElement().value; + }, + + getElementLabelText: function(value) { + return this.getElementLabel().innerHTML; + }, + + setElementValue: function(value) { + this.getElement().value = value; + }, + + setElementLabel: function(value) { + this.getElementLabel().innerHTML = value; + } +} diff --git a/js/prototype/validation.js b/js/prototype/validation.js index 312883d3ec..9cab7a2d56 100644 --- a/js/prototype/validation.js +++ b/js/prototype/validation.js @@ -97,7 +97,7 @@ Validation.prototype = { }, options || {}); if(this.options.onSubmit) Event.observe(this.form,'submit',this.onSubmit.bind(this),false); if(this.options.immediate) { - Form.getElements(this.form).each(function(input) { // Thanks Mike! + Form.getElements(this.form).each(function(input) { // Thanks Mike! if (input.tagName.toLowerCase() == 'select') { Event.observe(input, 'blur', this.onChange.bindAsEventListener(this)); } @@ -108,10 +108,10 @@ Validation.prototype = { onChange : function (ev) { Validation.isOnChange = true; Validation.validate(Event.element(ev),{ - useTitle : this.options.useTitles, + useTitle : this.options.useTitles, onElementValidate : this.options.onElementValidate }); - Validation.isOnChange = false; + Validation.isOnChange = false; }, onSubmit : function(ev){ if(!this.validate()) Event.stop(ev); @@ -122,9 +122,19 @@ Validation.prototype = { var callback = this.options.onElementValidate; try { if(this.options.stopOnFirst) { - result = Form.getElements(this.form).all(function(elm) { return Validation.validate(elm,{useTitle : useTitles, onElementValidate : callback}); }); + result = Form.getElements(this.form).all(function(elm) { + if (elm.hasClassName('local-validation') && !this.isElementInForm(elm, this.form)) { + return true; + } + return Validation.validate(elm,{useTitle : useTitles, onElementValidate : callback}); + }, this); } else { - result = Form.getElements(this.form).collect(function(elm) { return Validation.validate(elm,{useTitle : useTitles, onElementValidate : callback}); }).all(); + result = Form.getElements(this.form).collect(function(elm) { + if (elm.hasClassName('local-validation') && !this.isElementInForm(elm, this.form)) { + return true; + } + return Validation.validate(elm,{useTitle : useTitles, onElementValidate : callback}); + }, this).all(); } } catch (e) { @@ -142,6 +152,13 @@ Validation.prototype = { }, reset : function() { Form.getElements(this.form).each(Validation.reset); + }, + isElementInForm : function(elm, form) { + var domForm = elm.up('form'); + if (domForm == form) { + return true; + } + return false; } } @@ -244,7 +261,7 @@ Object.extend(Validation, { if (elm.type == 'radio' || elm.type == 'checkbox') { return elm.hasClassName('change-container-classname'); } - + return true; }, test : function(name, elm, useTitle) { @@ -264,8 +281,8 @@ Object.extend(Validation, { if (!elm.advaiceContainer) { elm.removeClassName('validation-passed'); elm.addClassName('validation-failed'); - } - + } + if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { var container = elm.up(Validation.defaultOptions.containerClassName); if (container && this.allowContainerClassName(elm)) { @@ -284,7 +301,7 @@ Object.extend(Validation, { if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { var container = elm.up(Validation.defaultOptions.containerClassName); if (container && !container.down('.validation-failed') && this.allowContainerClassName(elm)) { - if (!Validation.get('IsEmpty').test(elm.value) || !this.isVisible(elm)) { + if (!Validation.get('IsEmpty').test(elm.value) || !this.isVisible(elm)) { container.addClassName('validation-passed'); } else { container.removeClassName('validation-passed'); @@ -503,13 +520,13 @@ Validation.addAllThese([ }], ['validate-one-required-by-name', 'Please select one of the options.', function (v,elm) { var inputs = $$('input[name="' + elm.name.replace(/([\\"])/g, '\\$1') + '"]'); - + var error = 1; for(var i=0;i 0) this.index--; else this.index = this.entryCount-1; - this.getEntry(this.index).scrollIntoView(true); + //this.getEntry(this.index).scrollIntoView(true); useless }, markNext: function() { diff --git a/js/tiny_mce/blank.htm b/js/tiny_mce/blank.htm new file mode 100644 index 0000000000..5a4a5a57c7 --- /dev/null +++ b/js/tiny_mce/blank.htm @@ -0,0 +1,9 @@ + + + blank_page + + + + + + diff --git a/js/tiny_mce/classes/AddOnManager.js b/js/tiny_mce/classes/AddOnManager.js new file mode 100644 index 0000000000..8eb2458153 --- /dev/null +++ b/js/tiny_mce/classes/AddOnManager.js @@ -0,0 +1,95 @@ +/** + * $Id: PluginManager.js 352 2007-11-05 17:03:49Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; + + /**#@+ + * @class This class handles the loading of themes/plugins or other add-ons and their language packs. + * @member tinymce.AddOnManager + */ + tinymce.create('tinymce.AddOnManager', { + items : [], + urls : {}, + lookup : {}, + onAdd : new Dispatcher(this), + + /**#@+ + * @method + */ + + /** + * Returns the specified add on by the short name. + * + * @param {String} n Add-on to look for. + * @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined. + */ + get : function(n) { + return this.lookup[n]; + }, + + /** + * Loads a language pack for the specified add-on. + * + * @param {String} n Short name of the add-on. + */ + requireLangPack : function(n) { + var u, s = tinymce.EditorManager.settings; + + if (s && s.language) { + u = this.urls[n] + '/langs/' + s.language + '.js'; + + if (!tinymce.dom.Event.domLoaded && !s.strict_mode) + tinymce.ScriptLoader.load(u); + else + tinymce.ScriptLoader.add(u); + } + }, + + /** + * Adds a instance of the add-on by it's short name. + * + * @param {String} id Short name/id for the add-on. + * @param {tinymce.Theme/tinymce.Plugin} o Theme or plugin to add. + * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in. + */ + add : function(id, o) { + this.items.push(o); + this.lookup[id] = o; + this.onAdd.dispatch(this, id, o); + + return o; + }, + + /** + * Loads an add-on from a specific url. + * + * @param {String} n Short name of the add-on that gets loaded. + * @param {String} u URL to the add-on that will get loaded. + * @param {function} cb Optional callback to execute ones the add-on is loaded. + * @param {Object} s Optional scope to execute the callback in. + */ + 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)); \ No newline at end of file diff --git a/js/tiny_mce/classes/CommandManager.js b/js/tiny_mce/classes/CommandManager.js new file mode 100644 index 0000000000..6355630645 --- /dev/null +++ b/js/tiny_mce/classes/CommandManager.js @@ -0,0 +1,54 @@ +/** + * $Id: ControlManager.js 999 2009-02-10 17:42:58Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + tinymce.CommandManager = function() { + 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 tinymce.CommandManager(); +})(tinymce); \ No newline at end of file diff --git a/js/tiny_mce/classes/ControlManager.js b/js/tiny_mce/classes/ControlManager.js new file mode 100644 index 0000000000..6783ed9885 --- /dev/null +++ b/js/tiny_mce/classes/ControlManager.js @@ -0,0 +1,489 @@ +/** + * $Id: ControlManager.js 1150 2009-06-01 11:50:46Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + // Shorten names + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; + + /**#@+ + * @class This class is responsible for managing UI control instances. It's both a factory and a collection for the controls. + * @member tinymce.ControlManager + */ + tinymce.create('tinymce.ControlManager', { + /** + * Constructs a new control manager instance. + * Consult the Wiki for more details on this class. + * + * @constructor + * @param {tinymce.Editor} ed TinyMCE editor instance to add the control to. + * @param {Object} s Optional settings object for the control manager. + */ + 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(); + }); + }); + }, + + /**#@+ + * @method + */ + + /** + * Returns a control by id or undefined it it wasn't found. + * + * @param {String} id Control instance name. + * @return {tinymce.ui.Control} Control instance or undefined. + */ + get : function(id) { + return this.controls[this.prefix + id] || this.controls[id]; + }, + + /** + * Sets the active state of a control by id. + * + * @param {String} id Control id to set state on. + * @param {bool} s Active state true/false. + * @return {tinymce.ui.Control} Control instance that got activated or null if it wasn't found. + */ + setActive : function(id, s) { + var c = null; + + if (c = this.get(id)) + c.setActive(s); + + return c; + }, + + /** + * Sets the dsiabled state of a control by id. + * + * @param {String} id Control id to set state on. + * @param {bool} s Active state true/false. + * @return {tinymce.ui.Control} Control instance that got disabled or null if it wasn't found. + */ + setDisabled : function(id, s) { + var c = null; + + if (c = this.get(id)) + c.setDisabled(s); + + return c; + }, + + /** + * Adds a control to the control collection inside the manager. + * + * @param {tinymce.ui.Control} Control instance to add to collection. + * @return {tinymce.ui.Control} Control instance that got passed in. + */ + add : function(c) { + var t = this; + + if (c) { + t.controls[c.id] = c; + t.onAdd.dispatch(c, t); + } + + return c; + }, + + /** + * Creates a control by name, when a control is created it will automatically add it to the control collection. + * It first ask all plugins for the specified control if the plugins didn't return a control then the default behavior + * will be used. + * + * @param {String} n Control name to create for example "separator". + * @return {tinymce.ui.Control} Control instance that got created and added. + */ + 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); + }, + + /** + * Creates a drop menu control instance by id. + * + * @param {String} id Unique id for the new dropdown instance. For example "some menu". + * @param {Object} s Optional settings object for the control. + * @param {Object} cc Optional control class to use instead of the default one. + * @return {tinymce.ui.Control} Control instance that got created and added. + */ + 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) { + 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); + }, + + /** + * Creates a list box control instance by id. A list box is either a native select element or a DOM/JS based list box control. This + * depends on the use_native_selects settings state. + * + * @param {String} id Unique id for the new listbox instance. For example "styles". + * @param {Object} s Optional settings object for the control. + * @param {Object} cc Optional control class to use instead of the default one. + * @return {tinymce.ui.Control} Control instance that got created and added. + */ + 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); + }, + + /** + * Creates a button control instance by id. + * + * @param {String} id Unique id for the new button instance. For example "bold". + * @param {Object} s Optional settings object for the control. + * @param {Object} cc Optional control class to use instead of the default one. + * @return {tinymce.ui.Control} Control instance that got created and added. + */ + 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); + }, + + /** + * Creates a menu button control instance by id. + * + * @param {String} id Unique id for the new menu button instance. For example "menu1". + * @param {Object} s Optional settings object for the control. + * @param {Object} cc Optional control class to use instead of the default one. + * @return {tinymce.ui.Control} Control instance that got created and added. + */ + createMenuButton : function(id, s, cc) { + s = s || {}; + s.menu_button = 1; + + return this.createButton(id, s, cc); + }, + + /** + * Creates a split button control instance by id. + * + * @param {String} id Unique id for the new split button instance. For example "spellchecker". + * @param {Object} s Optional settings object for the control. + * @param {Object} cc Optional control class to use instead of the default one. + * @return {tinymce.ui.Control} Control instance that got created and added. + */ + 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; + }, + + /** + * Creates a color split button control instance by id. + * + * @param {String} id Unique id for the new color split button instance. For example "forecolor". + * @param {Object} s Optional settings object for the control. + * @param {Object} cc Optional control class to use instead of the default one. + * @return {tinymce.ui.Control} Control instance that got created and added. + */ + 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); + }, + + /** + * Creates a toolbar container control instance by id. + * + * @param {String} id Unique id for the new toolbar container control instance. For example "toolbar1". + * @param {Object} s Optional settings object for the control. + * @param {Object} cc Optional control class to use instead of the default one. + * @return {tinymce.ui.Control} Control instance that got created and added. + */ + 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); + }, + + /** + * Creates a separator control instance. + * + * @param {Object} cc Optional control class to use instead of the default one. + * @return {tinymce.ui.Control} Control instance that got created and added. + */ + createSeparator : function(cc) { + var cls = cc || this._cls.separator || tinymce.ui.Separator; + + return new cls(); + }, + + /** + * Overrides a specific control type with a custom class. + * + * @param {string} n Name of the control to override for example button or dropmenu. + * @param {function} c Class reference to use instead of the default one. + * @return {function} Same as the class reference. + */ + setControlType : function(n, c) { + return this._cls[n.toLowerCase()] = c; + }, + + destroy : function() { + each(this.controls, function(c) { + c.destroy(); + }); + + this.controls = null; + } + + /**#@-*/ + }); +})(tinymce); diff --git a/js/tiny_mce/classes/Developer.js b/js/tiny_mce/classes/Developer.js new file mode 100644 index 0000000000..08ce860f11 --- /dev/null +++ b/js/tiny_mce/classes/Developer.js @@ -0,0 +1,91 @@ +/** + * $Id: Developer.js 520 2008-01-07 16:30:32Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function() { + var EditorManager = tinymce.EditorManager, each = tinymce.each, DOM = tinymce.DOM; + + /** + * This class patches in various development features. This class is only available for the dev version of TinyMCE. + */ + tinymce.create('static tinymce.Developer', { + _piggyBack : function() { + var t = this, em = tinymce.EditorManager, lo = false; + + // Makes sure that XML language pack is used instead of JS files + t._runBefore(em, 'init', function(s) { + var par = new tinymce.xml.Parser({async : false}), lng = s.language || "en", i18n = tinymce.EditorManager.i18n, sl = tinymce.ScriptLoader; + + if (!s.translate_mode) + return; + + if (lo) + return; + + lo = true; + + // Common language loaded + sl.markDone(tinymce.baseURL + '/langs/' + lng + '.js'); + + // Theme languages loaded + sl.markDone(tinymce.baseURL + '/themes/simple/langs/' + lng + '.js'); + sl.markDone(tinymce.baseURL + '/themes/advanced/langs/' + lng + '.js'); + + // All plugin packs loaded + each(s.plugins.split(','), function(p) { + sl.markDone(tinymce.baseURL + '/plugins/' + p + '/langs/' + lng + '.js'); + }); + + // Load XML language pack + par.load(tinymce.baseURL + '/langs/' + lng + '.xml', function(doc, ex) { + var c; + + if (!doc) { + alert(ex.message); + return; + } + + if (doc.documentElement.nodeName == 'parsererror') { + alert('Parse error!!'); + return; + } + + c = doc.getElementsByTagName('language')[0].getAttribute("code"); + + each(doc.getElementsByTagName('group'), function(g) { + var gn = g.getAttribute("target"), o = {}; + + // Build object from XML items + each(g.getElementsByTagName('item'), function(it) { + var itn = it.getAttribute("name"); + + if (gn == "common") + i18n[c + '.' + itn] = par.getText(it); + else + i18n[c + '.' + gn + "." + itn] = par.getText(it); + }); + }); + }, { + async : false + }); + }); + }, + + _runBefore : function(o, n, f) { + var e = o[n]; + + o[n] = function() { + var s = f.apply(o, arguments); + + if (s !== false) + return e.apply(o, arguments); + }; + } + }); + + tinymce.Developer._piggyBack(); +})(); + diff --git a/js/tiny_mce/classes/Editor.js b/js/tiny_mce/classes/Editor.js new file mode 100644 index 0000000000..4a9ae0b9c8 --- /dev/null +++ b/js/tiny_mce/classes/Editor.js @@ -0,0 +1,2295 @@ +/** + * $Id: Editor.js 1165 2009-06-26 15:26:55Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, Dispatcher = tinymce.util.Dispatcher; + var each = tinymce.each, isGecko = tinymce.isGecko, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit; + var is = tinymce.is, ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, EditorManager = tinymce.EditorManager; + var inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode; + + /**#@+ + * @class This class contains the core logic for a TinyMCE editor. + * @member tinymce.Editor + */ + tinymce.create('tinymce.Editor', { + /** + * Constructs a editor instance by id. + * + * @constructor + * @member tinymce.Editor + * @param {String} id Unique id for the editor. + * @param {Object} s Optional settings string for the editor. + */ + Editor : function(id, s) { + var t = this; + + t.id = t.editorId = id; + t.execCommands = {}; + t.queryStateCommands = {}; + t.queryValueCommands = {}; + 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); + }); + + // Default editor config + 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 : '', + visual_table_class : 'mceItemTable', + visual : 1, + inline_styles : true, + convert_fonts_to_spans : true, + 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'; + + // 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 += ''; + + t.iframeHTML += ''; + + if (tinymce.relaxedDomain) + 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) { + // 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. + */ + 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); + } + + // 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, + update_styles : 1, + fix_ie_paragraphs : 1 + }); + + 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, + convert_fonts_to_spans : s.convert_fonts_to_spans, + font_size_classes : s.font_size_classes, + font_size_style_values : s.font_size_style_values, + apply_source_formatting : s.apply_source_formatting, + remove_linebreaks : s.remove_linebreaks, + element_format : s.element_format, + dom : t.dom + }); + + 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); + + 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('(' + v + ')>', 'g'), '' + n + '>'); + }); + }; + + 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.convert_fonts_to_spans) + t._convertFonts(); + + if (s.inline_styles) + t._convertInlineElements(); + + 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.undoManager.add({initial : true}); + 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 = EditorManager.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. + */ + 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, + strict_root : 1, + fix_ie_paragraphs : 1, + update_styles : 1 + }); + + 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, + convert_fonts_to_spans : s.convert_fonts_to_spans, + font_size_classes : s.font_size_classes, + font_size_style_values : s.font_size_style_values, + apply_source_formatting : s.apply_source_formatting, + dom : t.dom + }); + + 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); + + if (s.convert_fonts_to_spans) + t._convertFonts(); + + if (s.inline_styles) + t._convertInlineElements(); + + 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 EditorManager + * it will also place DOM focus inside the editor. + * + * @param {bool} 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 or the selection is outside the area in IE + // the IE statement is needed to avoid bluring if element selections inside layers since + // the layer is like it's own document in IE + if (!ce && (!isIE || t.selection.getNode().ownerDocument != t.getDoc())) + 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 (EditorManager.activeEditor != t) { + if ((oed = EditorManager.activeEditor) != null) + oed.onDeactivate.dispatch(oed, t); + + t.onActivate.dispatch(t, oed); + } + + EditorManager._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. + * + * @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. + * + * @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 = EditorManager.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. + * + * @param {String} n Name/key to get from the language pack. + * @param {String} dv Optional default value to retrive. + */ + getLang : function(n, dv) { + return EditorManager.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); + }, + + /** + * Returns a configuration parameter by name. + * + * @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. + * + * @param {Object} o Optional object to pass along for the node changed event. + */ + nodeChanged : function(o) { + var t = this, s = t.selection, n = s.getNode() || t.getBody(); + + // Fix for bug #1896577 it seems that this can not be fired while the editor is loading + if (t.initialized) { + t.onNodeChange.dispatch( + t, + o ? o.controlManager || t.controlManager : t.controlManager, + isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n, // Fix for IE initial state + 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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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 {bool} 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. + * + * @param {String} cmd Command name to execute, for example mceLink or Bold. + * @param {bool} 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 {bool} 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. + * + * @param {string} c Command to query state from. + * @return {bool} Command specific state, for example if bold is enabled or not. + */ + queryCommandState : function(c) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryStateCommands[c]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandState(c); + if (o !== -1) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandState(c); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + /** + * Returns a command specific value, for example the current font size. + * + * @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. + */ + 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. + */ + 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. + * + * @return {bool} 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. + * + * @param {bool} 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 {bool} 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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @return {bool} 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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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 EditorManager collection. + */ + 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 = []; + + EditorManager.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. + * + * @param {bool} 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); + } + + // 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, '']); + + 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) { + t.selection.getRng().item(0).removeNode(); + 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(); + }; + + // Add undo level on editor blur + if (tinymce.isIE) { + t.dom.bind(t.getWin(), 'blur', function(e) { + var n; + + // Check added for fullscreen bug + if (t.selection) { + n = t.selection.getNode(); + + // Add undo level is selection was lost to another document + if (!t.removed && n.ownerDocument && n.ownerDocument != t.getDoc()) + addUndo(); + } + }); + } else { + t.dom.bind(t.getDoc(), 'blur', function() { + if (t.selection && !t.removed) + addUndo(); + }); + } + + t.onMouseDown.add(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) { + t.undoManager.typing = 0; + t.undoManager.add(); + } + }); + + 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) { + t.undoManager.add(); + t.undoManager.typing = 0; + } + + return; + } + + if (!t.undoManager.typing) { + t.undoManager.add(); + t.undoManager.typing = 1; + } + }); + } + }, + + _convertInlineElements : function() { + var t = this, s = t.settings, dom = t.dom, v, e, na, st, sp; + + function convert(ed, o) { + if (!s.inline_styles) + return; + + if (o.get) { + each(t.dom.select('table,u,strike', o.node), function(n) { + switch (n.nodeName) { + case 'TABLE': + if (v = dom.getAttrib(n, 'height')) { + dom.setStyle(n, 'height', v); + dom.setAttrib(n, 'height', ''); + } + break; + + case 'U': + case 'STRIKE': + //sp = dom.create('span', {style : dom.getAttrib(n, 'style')}); + n.style.textDecoration = n.nodeName == 'U' ? 'underline' : 'line-through'; + dom.setAttrib(n, 'mce_style', ''); + dom.setAttrib(n, 'mce_name', 'span'); + break; + } + }); + } else if (o.set) { + each(t.dom.select('table,span', o.node).reverse(), function(n) { + if (n.nodeName == 'TABLE') { + if (v = dom.getStyle(n, 'height')) + dom.setAttrib(n, 'height', v.replace(/[^0-9%]+/g, '')); + } else { + // Convert spans to elements + if (n.style.textDecoration == 'underline') + na = 'u'; + else if (n.style.textDecoration == 'line-through') + na = 'strike'; + else + na = ''; + + if (na) { + n.style.textDecoration = ''; + dom.setAttrib(n, 'mce_style', ''); + + e = dom.create(na, { + style : dom.getAttrib(n, 'style') + }); + + dom.replace(e, n, 1); + } + } + }); + } + }; + + t.onPreProcess.add(convert); + + if (!s.cleanup_on_startup) { + t.onSetContent.add(function(ed, o) { + if (o.initial) + convert(t, {node : t.getBody(), set : 1}); + }); + } + }, + + _convertFonts : function() { + var t = this, s = t.settings, dom = t.dom, fz, fzn, sl, cl; + + // No need + if (!s.inline_styles) + return; + + // Font pt values and font size names + fz = [8, 10, 12, 14, 18, 24, 36]; + fzn = ['xx-small', 'x-small','small','medium','large','x-large', 'xx-large']; + + if (sl = s.font_size_style_values) + sl = explode(sl); + + if (cl = s.font_size_classes) + cl = explode(cl); + + function process(no) { + var n, sp, nl, x; + + // Keep unit tests happy + if (!s.inline_styles) + return; + + nl = t.dom.select('font', no); + for (x = nl.length - 1; x >= 0; x--) { + n = nl[x]; + + sp = dom.create('span', { + style : dom.getAttrib(n, 'style'), + 'class' : dom.getAttrib(n, 'class') + }); + + dom.setStyles(sp, { + fontFamily : dom.getAttrib(n, 'face'), + color : dom.getAttrib(n, 'color'), + backgroundColor : n.style.backgroundColor + }); + + if (n.size) { + if (sl) + dom.setStyle(sp, 'fontSize', sl[parseInt(n.size) - 1]); + else + dom.setAttrib(sp, 'class', cl[parseInt(n.size) - 1]); + } + + dom.setAttrib(sp, 'mce_style', ''); + dom.replace(sp, n, 1); + } + }; + + // Run on cleanup + t.onPreProcess.add(function(ed, o) { + if (o.get) + process(o.node); + }); + + t.onSetContent.add(function(ed, o) { + if (o.initial) + process(o.node); + }); + }, + + _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[d.length - 1].tag + '>'; + 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 += '' + d[i].tag + '>'; + + return s; + } + + /**#@-*/ + }); +})(tinymce); diff --git a/js/tiny_mce/classes/EditorCommands.js b/js/tiny_mce/classes/EditorCommands.js new file mode 100644 index 0000000000..d9997dcec3 --- /dev/null +++ b/js/tiny_mce/classes/EditorCommands.js @@ -0,0 +1,934 @@ +/** + * $Id: EditorCommands.js 1070 2009-04-01 18:03:06Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + var each = tinymce.each, isIE = tinymce.isIE, isGecko = tinymce.isGecko, isOpera = tinymce.isOpera, isWebKit = tinymce.isWebKit; + + /** + * This is a internal class and no method in this class should be called directly form the out side. + */ + tinymce.create('tinymce.EditorCommands', { + EditorCommands : function(ed) { + this.editor = ed; + }, + + execCommand : function(cmd, ui, val) { + var t = this, ed = t.editor, f; + + switch (cmd) { + // Ignore these + case 'mceResetDesignMode': + case 'mceBeginUndoLevel': + return true; + + // Ignore these + case 'unlink': + t.UnLink(); + return true; + + // Bundle these together + case 'JustifyLeft': + case 'JustifyCenter': + case 'JustifyRight': + case 'JustifyFull': + t.mceJustify(cmd, cmd.substring(7).toLowerCase()); + return true; + + default: + f = this[cmd]; + + if (f) { + f.call(this, ui, val); + return true; + } + } + + return false; + }, + + Indent : function() { + var ed = this.editor, d = ed.dom, s = ed.selection, e, iv, iu; + + // Setup indent level + iv = ed.settings.indentation; + iu = /[a-z%]+$/i.exec(iv); + iv = parseInt(iv); + + if (ed.settings.inline_styles && (!this.queryStateInsertUnorderedList() && !this.queryStateInsertOrderedList())) { + each(s.getSelectedBlocks(), function(e) { + d.setStyle(e, 'paddingLeft', (parseInt(e.style.paddingLeft || 0) + iv) + iu); + }); + + return; + } + + ed.getDoc().execCommand('Indent', false, null); + + if (isIE) { + d.getParent(s.getNode(), function(n) { + if (n.nodeName == 'BLOCKQUOTE') { + n.dir = n.style.cssText = ''; + } + }); + } + }, + + Outdent : function() { + var ed = this.editor, d = ed.dom, s = ed.selection, e, v, iv, iu; + + // Setup indent level + iv = ed.settings.indentation; + iu = /[a-z%]+$/i.exec(iv); + iv = parseInt(iv); + + if (ed.settings.inline_styles && (!this.queryStateInsertUnorderedList() && !this.queryStateInsertOrderedList())) { + each(s.getSelectedBlocks(), function(e) { + v = Math.max(0, parseInt(e.style.paddingLeft || 0) - iv); + d.setStyle(e, 'paddingLeft', v ? v + iu : ''); + }); + + return; + } + + ed.getDoc().execCommand('Outdent', false, null); + }, + +/* + mceSetAttribute : function(u, v) { + var ed = this.editor, d = ed.dom, e; + + if (e = d.getParent(ed.selection.getNode(), d.isBlock)) + d.setAttrib(e, v.name, v.value); + }, +*/ + mceSetContent : function(u, v) { + this.editor.setContent(v); + }, + + mceToggleVisualAid : function() { + var ed = this.editor; + + ed.hasVisual = !ed.hasVisual; + ed.addVisual(); + }, + + mceReplaceContent : function(u, v) { + var s = this.editor.selection; + + s.setContent(v.replace(/\{\$selection\}/g, s.getContent({format : 'text'}))); + }, + + mceInsertLink : function(u, v) { + var ed = this.editor, s = ed.selection, e = ed.dom.getParent(s.getNode(), 'a'); + + if (tinymce.is(v, 'string')) + v = {href : v}; + + function set(e) { + each(v, function(v, k) { + ed.dom.setAttrib(e, k, v); + }); + }; + + if (!e) { + ed.execCommand('CreateLink', false, 'javascript:mctmp(0);'); + each(ed.dom.select('a[href=javascript:mctmp(0);]'), function(e) { + set(e); + }); + } else { + if (v.href) + set(e); + else + ed.dom.remove(e, 1); + } + }, + + UnLink : function() { + var ed = this.editor, s = ed.selection; + + if (s.isCollapsed()) + s.select(s.getNode()); + + ed.getDoc().execCommand('unlink', false, null); + s.collapse(0); + }, + + FontName : function(u, v) { + var t = this, ed = t.editor, s = ed.selection, e; + + if (!v) { + if (s.isCollapsed()) + s.select(s.getNode()); + } else { + if (ed.settings.convert_fonts_to_spans) + t._applyInlineStyle('span', {style : {fontFamily : v}}); + else + ed.getDoc().execCommand('FontName', false, v); + } + }, + + FontSize : function(u, v) { + var ed = this.editor, s = ed.settings, fc, fs; + + // Use style options instead + if (s.convert_fonts_to_spans && v >= 1 && v <= 7) { + fs = tinymce.explode(s.font_size_style_values); + fc = tinymce.explode(s.font_size_classes); + + if (fc) + v = fc[v - 1] || v; + else + v = fs[v - 1] || v; + } + + if (v >= 1 && v <= 7) + ed.getDoc().execCommand('FontSize', false, v); + else + this._applyInlineStyle('span', {style : {fontSize : v}}); + }, + + queryCommandValue : function(c) { + var f = this['queryValue' + c]; + + if (f) + return f.call(this, c); + + return false; + }, + + queryCommandState : function(cmd) { + var f; + + switch (cmd) { + // Bundle these together + case 'JustifyLeft': + case 'JustifyCenter': + case 'JustifyRight': + case 'JustifyFull': + return this.queryStateJustify(cmd, cmd.substring(7).toLowerCase()); + + default: + if (f = this['queryState' + cmd]) + return f.call(this, cmd); + } + + return -1; + }, + + _queryState : function(c) { + try { + return this.editor.getDoc().queryCommandState(c); + } catch (ex) { + // Ignore exception + } + }, + + _queryVal : function(c) { + try { + return this.editor.getDoc().queryCommandValue(c); + } catch (ex) { + // Ignore exception + } + }, + + queryValueFontSize : function() { + var ed = this.editor, v = 0, p; + + if (p = ed.dom.getParent(ed.selection.getNode(), 'span')) + v = p.style.fontSize; + + if (!v && (isOpera || isWebKit)) { + if (p = ed.dom.getParent(ed.selection.getNode(), 'font')) + v = p.size; + + return v; + } + + return v || this._queryVal('FontSize'); + }, + + queryValueFontName : function() { + var ed = this.editor, v = 0, p; + + if (p = ed.dom.getParent(ed.selection.getNode(), 'font')) + v = p.face; + + if (p = ed.dom.getParent(ed.selection.getNode(), 'span')) + v = p.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); + + if (!v) + v = this._queryVal('FontName'); + + return v; + }, + + mceJustify : function(c, v) { + var ed = this.editor, se = ed.selection, n = se.getNode(), nn = n.nodeName, bl, nb, dom = ed.dom, rm; + + if (ed.settings.inline_styles && this.queryStateJustify(c, v)) + rm = 1; + + bl = dom.getParent(n, ed.dom.isBlock); + + if (nn == 'IMG') { + if (v == 'full') + return; + + if (rm) { + if (v == 'center') + dom.setStyle(bl || n.parentNode, 'textAlign', ''); + + dom.setStyle(n, 'float', ''); + this.mceRepaint(); + return; + } + + if (v == 'center') { + // Do not change table elements + if (bl && /^(TD|TH)$/.test(bl.nodeName)) + bl = 0; + + if (!bl || bl.childNodes.length > 1) { + nb = dom.create('p'); + nb.appendChild(n.cloneNode(false)); + + if (bl) + dom.insertAfter(nb, bl); + else + dom.insertAfter(nb, n); + + dom.remove(n); + n = nb.firstChild; + bl = nb; + } + + dom.setStyle(bl, 'textAlign', v); + dom.setStyle(n, 'float', ''); + } else { + dom.setStyle(n, 'float', v); + dom.setStyle(bl || n.parentNode, 'textAlign', ''); + } + + this.mceRepaint(); + return; + } + + // Handle the alignment outselfs, less quirks in all browsers + if (ed.settings.inline_styles && ed.settings.forced_root_block) { + if (rm) + v = ''; + + each(se.getSelectedBlocks(dom.getParent(se.getStart(), dom.isBlock), dom.getParent(se.getEnd(), dom.isBlock)), function(e) { + dom.setAttrib(e, 'align', ''); + dom.setStyle(e, 'textAlign', v == 'full' ? 'justify' : v); + }); + + return; + } else if (!rm) + ed.getDoc().execCommand(c, false, null); + + if (ed.settings.inline_styles) { + if (rm) { + dom.getParent(ed.selection.getNode(), function(n) { + if (n.style && n.style.textAlign) + dom.setStyle(n, 'textAlign', ''); + }); + + return; + } + + each(dom.select('*'), function(n) { + var v = n.align; + + if (v) { + if (v == 'full') + v = 'justify'; + + dom.setStyle(n, 'textAlign', v); + dom.setAttrib(n, 'align', ''); + } + }); + } + }, + + mceSetCSSClass : function(u, v) { + this.mceSetStyleInfo(0, {command : 'setattrib', name : 'class', value : v}); + }, + + getSelectedElement : function() { + var t = this, ed = t.editor, dom = ed.dom, se = ed.selection, r = se.getRng(), r1, r2, sc, ec, so, eo, e, sp, ep, re; + + if (se.isCollapsed() || r.item) + return se.getNode(); + + // Setup regexp + re = ed.settings.merge_styles_invalid_parents; + if (tinymce.is(re, 'string')) + re = new RegExp(re, 'i'); + + if (isIE) { + r1 = r.duplicate(); + r1.collapse(true); + sc = r1.parentElement(); + + r2 = r.duplicate(); + r2.collapse(false); + ec = r2.parentElement(); + + if (sc != ec) { + r1.move('character', 1); + sc = r1.parentElement(); + } + + if (sc == ec) { + r1 = r.duplicate(); + r1.moveToElementText(sc); + + if (r1.compareEndPoints('StartToStart', r) == 0 && r1.compareEndPoints('EndToEnd', r) == 0) + return re && re.test(sc.nodeName) ? null : sc; + } + } else { + function getParent(n) { + return dom.getParent(n, '*'); + }; + + sc = r.startContainer; + ec = r.endContainer; + so = r.startOffset; + eo = r.endOffset; + + if (!r.collapsed) { + if (sc == ec) { + if (so - eo < 2) { + if (sc.hasChildNodes()) { + sp = sc.childNodes[so]; + return re && re.test(sp.nodeName) ? null : sp; + } + } + } + } + + if (sc.nodeType != 3 || ec.nodeType != 3) + return null; + + if (so == 0) { + sp = getParent(sc); + + if (sp && sp.firstChild != sc) + sp = null; + } + + if (so == sc.nodeValue.length) { + e = sc.nextSibling; + + if (e && e.nodeType == 1) + sp = sc.nextSibling; + } + + if (eo == 0) { + e = ec.previousSibling; + + if (e && e.nodeType == 1) + ep = e; + } + + if (eo == ec.nodeValue.length) { + ep = getParent(ec); + + if (ep && ep.lastChild != ec) + ep = null; + } + + // Same element + if (sp == ep) + return re && sp && re.test(sp.nodeName) ? null : sp; + } + + return null; + }, + + mceSetStyleInfo : function(u, v) { + var t = this, ed = t.editor, d = ed.getDoc(), dom = ed.dom, e, b, s = ed.selection, nn = v.wrapper || 'span', b = s.getBookmark(), re; + + function set(n, e) { + if (n.nodeType == 1) { + switch (v.command) { + case 'setattrib': + return dom.setAttrib(n, v.name, v.value); + + case 'setstyle': + return dom.setStyle(n, v.name, v.value); + + case 'removeformat': + return dom.setAttrib(n, 'class', ''); + } + } + }; + + // Setup regexp + re = ed.settings.merge_styles_invalid_parents; + if (tinymce.is(re, 'string')) + re = new RegExp(re, 'i'); + + // Set style info on selected element + if ((e = t.getSelectedElement()) && !ed.settings.force_span_wrappers) + set(e, 1); + else { + // Generate wrappers and set styles on them + d.execCommand('FontName', false, '__'); + each(dom.select('span,font'), function(n) { + var sp, e; + + if (dom.getAttrib(n, 'face') == '__' || n.style.fontFamily === '__') { + sp = dom.create(nn, {mce_new : '1'}); + + set(sp); + + each (n.childNodes, function(n) { + sp.appendChild(n.cloneNode(true)); + }); + + dom.replace(sp, n); + } + }); + } + + // Remove wrappers inside new ones + each(dom.select(nn).reverse(), function(n) { + var p = n.parentNode; + + // Check if it's an old span in a new wrapper + if (!dom.getAttrib(n, 'mce_new')) { + // Find new wrapper + p = dom.getParent(n, '*[mce_new]'); + + if (p) + dom.remove(n, 1); + } + }); + + // Merge wrappers with parent wrappers + each(dom.select(nn).reverse(), function(n) { + var p = n.parentNode; + + if (!p || !dom.getAttrib(n, 'mce_new')) + return; + + if (ed.settings.force_span_wrappers && p.nodeName != 'SPAN') + return; + + // Has parent of the same type and only child + if (p.nodeName == nn.toUpperCase() && p.childNodes.length == 1) + return dom.remove(p, 1); + + // Has parent that is more suitable to have the class and only child + if (n.nodeType == 1 && (!re || !re.test(p.nodeName)) && p.childNodes.length == 1) { + set(p); // Set style info on parent instead + dom.setAttrib(n, 'class', ''); + } + }); + + // Remove empty wrappers + each(dom.select(nn).reverse(), function(n) { + if (dom.getAttrib(n, 'mce_new') || (dom.getAttribs(n).length <= 1 && n.className === '')) { + if (!dom.getAttrib(n, 'class') && !dom.getAttrib(n, 'style')) + return dom.remove(n, 1); + + dom.setAttrib(n, 'mce_new', ''); // Remove mce_new marker + } + }); + + s.moveToBookmark(b); + }, + + queryStateJustify : function(c, v) { + var ed = this.editor, n = ed.selection.getNode(), dom = ed.dom; + + if (n && n.nodeName == 'IMG') { + if (dom.getStyle(n, 'float') == v) + return 1; + + return n.parentNode.style.textAlign == v; + } + + n = dom.getParent(ed.selection.getStart(), function(n) { + return n.nodeType == 1 && n.style.textAlign; + }); + + if (v == 'full') + v = 'justify'; + + if (ed.settings.inline_styles) + return (n && n.style.textAlign == v); + + return this._queryState(c); + }, + + ForeColor : function(ui, v) { + var ed = this.editor; + + if (ed.settings.convert_fonts_to_spans) { + this._applyInlineStyle('span', {style : {color : v}}); + return; + } else + ed.getDoc().execCommand('ForeColor', false, v); + }, + + HiliteColor : function(ui, val) { + var t = this, ed = t.editor, d = ed.getDoc(); + + if (ed.settings.convert_fonts_to_spans) { + this._applyInlineStyle('span', {style : {backgroundColor : val}}); + return; + } + + function set(s) { + if (!isGecko) + return; + + try { + // Try new Gecko method + d.execCommand("styleWithCSS", 0, s); + } catch (ex) { + // Use old + d.execCommand("useCSS", 0, !s); + } + }; + + if (isGecko || isOpera) { + set(true); + d.execCommand('hilitecolor', false, val); + set(false); + } else + d.execCommand('BackColor', false, val); + }, + + FormatBlock : function(ui, val) { + var t = this, ed = t.editor, s = ed.selection, dom = ed.dom, bl, nb, b; + + function isBlock(n) { + return /^(P|DIV|H[1-6]|ADDRESS|BLOCKQUOTE|PRE)$/.test(n.nodeName); + }; + + bl = dom.getParent(s.getNode(), function(n) { + return isBlock(n); + }); + + // IE has an issue where it removes the parent div if you change format on the paragrah in Content + // FF and Opera doesn't change parent DIV elements if you switch format + if (bl) { + if ((isIE && isBlock(bl.parentNode)) || bl.nodeName == 'DIV') { + // Rename block element + nb = ed.dom.create(val); + + each(dom.getAttribs(bl), function(v) { + dom.setAttrib(nb, v.nodeName, dom.getAttrib(bl, v.nodeName)); + }); + + b = s.getBookmark(); + dom.replace(nb, bl, 1); + s.moveToBookmark(b); + ed.nodeChanged(); + return; + } + } + + val = ed.settings.forced_root_block ? (val || '') : val; + + if (val.indexOf('<') == -1) + val = '<' + val + '>'; + + if (tinymce.isGecko) + val = val.replace(/<(div|blockquote|code|dt|dd|dl|samp)>/gi, '$1'); + + ed.getDoc().execCommand('FormatBlock', false, val); + }, + + mceCleanup : function() { + var ed = this.editor, s = ed.selection, b = s.getBookmark(); + ed.setContent(ed.getContent()); + s.moveToBookmark(b); + }, + + mceRemoveNode : function(ui, val) { + var ed = this.editor, s = ed.selection, b, n = val || s.getNode(); + + // Make sure that the body node isn't removed + if (n == ed.getBody()) + return; + + b = s.getBookmark(); + ed.dom.remove(n, 1); + s.moveToBookmark(b); + ed.nodeChanged(); + }, + + mceSelectNodeDepth : function(ui, val) { + var ed = this.editor, s = ed.selection, c = 0; + + ed.dom.getParent(s.getNode(), function(n) { + if (n.nodeType == 1 && c++ == val) { + s.select(n); + ed.nodeChanged(); + return false; + } + }, ed.getBody()); + }, + + mceSelectNode : function(u, v) { + this.editor.selection.select(v); + }, + + mceInsertContent : function(ui, val) { + this.editor.selection.setContent(val); + }, + + mceInsertRawHTML : function(ui, val) { + var ed = this.editor; + + ed.selection.setContent('tiny_mce_marker'); + ed.setContent(ed.getContent().replace(/tiny_mce_marker/g, val)); + }, + + mceRepaint : function() { + var s, b, e = this.editor; + + if (tinymce.isGecko) { + try { + s = e.selection; + b = s.getBookmark(true); + + if (s.getSel()) + s.getSel().selectAllChildren(e.getBody()); + + s.collapse(true); + s.moveToBookmark(b); + } catch (ex) { + // Ignore + } + } + }, + + queryStateUnderline : function() { + var ed = this.editor, n = ed.selection.getNode(); + + if (n && n.nodeName == 'A') + return false; + + return this._queryState('Underline'); + }, + + queryStateOutdent : function() { + var ed = this.editor, n; + + if (ed.settings.inline_styles) { + if ((n = ed.dom.getParent(ed.selection.getStart(), ed.dom.isBlock)) && parseInt(n.style.paddingLeft) > 0) + return true; + + if ((n = ed.dom.getParent(ed.selection.getEnd(), ed.dom.isBlock)) && parseInt(n.style.paddingLeft) > 0) + return true; + } + + return this.queryStateInsertUnorderedList() || this.queryStateInsertOrderedList() || (!ed.settings.inline_styles && !!ed.dom.getParent(ed.selection.getNode(), 'BLOCKQUOTE')); + }, + + queryStateInsertUnorderedList : function() { + return this.editor.dom.getParent(this.editor.selection.getNode(), 'UL'); + }, + + queryStateInsertOrderedList : function() { + return this.editor.dom.getParent(this.editor.selection.getNode(), 'OL'); + }, + + queryStatemceBlockQuote : function() { + return !!this.editor.dom.getParent(this.editor.selection.getStart(), function(n) {return n.nodeName === 'BLOCKQUOTE';}); + }, + + _applyInlineStyle : function(na, at, op) { + var t = this, ed = t.editor, dom = ed.dom, bm, lo = {}, kh, found; + + na = na.toUpperCase(); + + if (op && op.check_classes && at['class']) + op.check_classes.push(at['class']); + + function removeEmpty() { + each(dom.select(na).reverse(), function(n) { + var c = 0; + + // Check if there is any attributes + each(dom.getAttribs(n), function(an) { + if (an.nodeName.substring(0, 1) != '_' && dom.getAttrib(n, an.nodeName) != '') { + //console.log(dom.getOuterHTML(n), dom.getAttrib(n, an.nodeName)); + c++; + } + }); + + // No attributes then remove the element and keep the children + if (c == 0) + dom.remove(n, 1); + }); + }; + + function replaceFonts() { + var bm; + + each(dom.select('span,font'), function(n) { + if (n.style.fontFamily == 'mceinline' || n.face == 'mceinline') { + if (!bm) + bm = ed.selection.getBookmark(); + + at._mce_new = '1'; + dom.replace(dom.create(na, at), n, 1); + } + }); + + // Remove redundant elements + each(dom.select(na + '[_mce_new]'), function(n) { + function removeStyle(n) { + if (n.nodeType == 1) { + each(at.style, function(v, k) { + dom.setStyle(n, k, ''); + }); + + // Remove spans with the same class or marked classes + if (at['class'] && n.className && op) { + each(op.check_classes, function(c) { + if (dom.hasClass(n, c)) + dom.removeClass(n, c); + }); + } + } + }; + + // Remove specified style information from child elements + each(dom.select(na, n), removeStyle); + + // Remove the specified style information on parent if current node is only child (IE) + if (n.parentNode && n.parentNode.nodeType == 1 && n.parentNode.childNodes.length == 1) + removeStyle(n.parentNode); + + // Remove the child elements style info if a parent already has it + dom.getParent(n.parentNode, function(pn) { + if (pn.nodeType == 1) { + if (at.style) { + each(at.style, function(v, k) { + var sv; + + if (!lo[k] && (sv = dom.getStyle(pn, k))) { + if (sv === v) + dom.setStyle(n, k, ''); + + lo[k] = 1; + } + }); + } + + // Remove spans with the same class or marked classes + if (at['class'] && pn.className && op) { + each(op.check_classes, function(c) { + if (dom.hasClass(pn, c)) + dom.removeClass(n, c); + }); + } + } + + return false; + }); + + n.removeAttribute('_mce_new'); + }); + + removeEmpty(); + ed.selection.moveToBookmark(bm); + + return !!bm; + }; + + // Create inline elements + ed.focus(); + ed.getDoc().execCommand('FontName', false, 'mceinline'); + replaceFonts(); + + if (kh = t._applyInlineStyle.keyhandler) { + ed.onKeyUp.remove(kh); + ed.onKeyPress.remove(kh); + ed.onKeyDown.remove(kh); + ed.onSetContent.remove(t._applyInlineStyle.chandler); + } + + if (ed.selection.isCollapsed()) { + // IE will format the current word so this code can't be executed on that browser + if (!isIE) { + each(dom.getParents(ed.selection.getNode(), 'span'), function(n) { + each(at.style, function(v, k) { + var kv; + + if (kv = dom.getStyle(n, k)) { + if (kv == v) { + dom.setStyle(n, k, ''); + found = 2; + return false; + } + + found = 1; + return false; + } + }); + + if (found) + return false; + }); + + if (found == 2) { + bm = ed.selection.getBookmark(); + + removeEmpty(); + + ed.selection.moveToBookmark(bm); + + // Node change needs to be detached since the onselect event + // for the select box will run the onclick handler after onselect call. Todo: Add a nicer fix! + window.setTimeout(function() { + ed.nodeChanged(); + }, 1); + + return; + } + } + + // Start collecting styles + t._pendingStyles = tinymce.extend(t._pendingStyles || {}, at.style); + + t._applyInlineStyle.chandler = ed.onSetContent.add(function() { + delete t._pendingStyles; + }); + + t._applyInlineStyle.keyhandler = kh = function(e) { + // Use pending styles + if (t._pendingStyles) { + at.style = t._pendingStyles; + delete t._pendingStyles; + } + + if (replaceFonts()) { + ed.onKeyDown.remove(t._applyInlineStyle.keyhandler); + ed.onKeyPress.remove(t._applyInlineStyle.keyhandler); + } + + if (e.type == 'keyup') + ed.onKeyUp.remove(t._applyInlineStyle.keyhandler); + }; + + ed.onKeyDown.add(kh); + ed.onKeyPress.add(kh); + ed.onKeyUp.add(kh); + } else + t._pendingStyles = 0; + } + }); +})(tinymce); \ No newline at end of file diff --git a/js/tiny_mce/classes/EditorManager.js b/js/tiny_mce/classes/EditorManager.js new file mode 100644 index 0000000000..69d8970449 --- /dev/null +++ b/js/tiny_mce/classes/EditorManager.js @@ -0,0 +1,453 @@ +/** + * $Id: EditorManager.js 1160 2009-06-18 18:54:44Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(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; + + /**#@+ + * @class This class is used to create multiple editor instances and contain them in a collection. So it's both a factory and a manager for editor instances. + * @static + * @member tinymce.EditorManager + */ + tinymce.create('static tinymce.EditorManager', { + editors : {}, + i18n : {}, + activeEditor : null, + + /**#@+ + * @method + */ + + /** + * Preinitializes the EditorManager class. This method will be called automatically when the page loads and it + * will setup some important paths and URIs and attach some document events. + */ + preInit : function() { + var t = this, lo = window.location; + + // Setup some URLs where the editor API is located and where the document is + tinymce.documentBaseURL = lo.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); + if (!/[\/\\]$/.test(tinymce.documentBaseURL)) + tinymce.documentBaseURL += '/'; + + tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); + tinymce.EditorManager.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 + t.onBeforeUnload = new tinymce.util.Dispatcher(t); + + // Must be on window or IE will leak if the editor is placed in frame or iframe + Event.add(window, 'beforeunload', function(e) { + t.onBeforeUnload.dispatch(t, e); + }); + }, + + /** + * Initializes a set of editors. This method will create a bunch of editors based in the input. + * + * @param {Object} s Settings object to be passed to each editor instance. + */ + init : function(s) { + var t = this, pl, sl = tinymce.ScriptLoader, c, 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", + strict_loading_mode : document.contentType == 'application/xhtml+xml' + }, s); + + t.settings = s; + + // If page not loaded and strict mode isn't enabled then load them + if (!Event.domLoaded && !s.strict_loading_mode) { + // Load language + if (s.language) + sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); + + // Load theme + if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) + ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); + + // Load plugins + if (s.plugins) { + pl = explode(s.plugins); + + // Load compat2x first + if (tinymce.inArray(pl, 'compat2x') != -1) + PluginManager.load('compat2x', 'plugins/compat2x/editor_plugin' + tinymce.suffix + '.js'); + + // Load rest if plugins + each(pl, function(v) { + if (v && v.charAt(0) != '-' && !PluginManager.urls[v]) { + // Skip safari plugin for other browsers + if (!tinymce.isWebKit && v == 'safari') + return; + + PluginManager.load(v, 'plugins/' + v + '/editor_plugin' + tinymce.suffix + '.js'); + } + }); + } + + sl.loadQueue(); + } + + // Legacy call + Event.add(document, 'init', function() { + var l, co; + + execCallback(s, 'onpageload'); + + // Verify that it's a valid browser + if (s.browsers) { + l = false; + + each(explode(s.browsers), function(v) { + switch (v) { + case 'ie': + case 'msie': + if (tinymce.isIE) + l = true; + break; + + case 'gecko': + if (tinymce.isGecko) + l = true; + break; + + case 'safari': + case 'webkit': + if (tinymce.isWebKit) + l = true; + break; + + case 'opera': + if (tinymce.isOpera) + l = true; + + break; + } + }); + + // Not a valid one + if (!l) + return; + } + + 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 { + c = 0; + + each(document.forms, function(f) { + each(f.elements, function(e) { + if (e.name === v) { + v = 'mce_editor_' + c; + 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. + * + * @param {String} id Editor instance id to return. + * @return {tinymce.Editor} Editor instance to return. + */ + get : function(id) { + return this.editors[id]; + }, + + /** + * Returns a editor instance by id. This method was added for compatibility with the 2.x branch. + * + * @param {String} id Editor instance id to return. + * @return {tinymce.Editor} Editor instance to return. + */ + getInstanceById : function(id) { + return this.get(id); + }, + + /** + * Adds an editor instance to the editor collection. This will also set it as the active editor. + * + * @param {tinymce.Editor} e Editor instance to add to the collection. + * @return {tinymce.Editor} The same instance that got passed in. + */ + add : function(e) { + this.editors[e.id] = e; + this._setActive(e); + + return e; + }, + + /** + * Removes a editor instance from the collection. + * + * @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(e) { + var t = this; + + // Not in the collection + if (!t.editors[e.id]) + return null; + + delete t.editors[e.id]; + + // Select another editor since the active one was removed + if (t.activeEditor == e) { + t._setActive(null); + + each(t.editors, function(e) { + t._setActive(e); + return false; // Break + }); + } + + e.destroy(); + + return e; + }, + + /** + * Executes a specific command on the currently active editor. + * + * @param {String} c Command to perform for example Bold. + * @param {bool} 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 {bool} 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. + * + * @param {String} id Editor id to perform the command on. + * @param {String} c Command to perform for example Bold. + * @param {bool} 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 {bool} 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. + */ + triggerSave : function() { + each(this.editors, function(e) { + e.save(); + }); + }, + + /** + * Adds a language pack, this gets called by the loaded language files like en.js. + * + * @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(e) { + this.selectedInstance = this.activeEditor = e; + } + + /**#@-*/ + }); + + tinymce.EditorManager.preInit(); +})(tinymce); + +// Short for editor manager window.tinyMCE is needed when TinyMCE gets loaded though a XHR call +var tinyMCE = window.tinyMCE = tinymce.EditorManager; diff --git a/js/tiny_mce/classes/ForceBlocks.js b/js/tiny_mce/classes/ForceBlocks.js new file mode 100644 index 0000000000..c0a4969781 --- /dev/null +++ b/js/tiny_mce/classes/ForceBlocks.js @@ -0,0 +1,644 @@ +/** + * $Id: ForceBlocks.js 1137 2009-05-22 15:13:40Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + // Shorten names + var Event, isIE, isGecko, isOpera, each, extend; + + Event = tinymce.dom.Event; + isIE = tinymce.isIE; + isGecko = tinymce.isGecko; + isOpera = tinymce.isOpera; + each = tinymce.each; + extend = tinymce.extend; + + 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(/[ \t\r\n]+/g, '') == ''; + }; + + /** + * 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, '' + elm + '>'); + + o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0' + elm + '>'); + + if (!isIE && !isOpera && o.set) { + // Use instead of BR in padded paragraphs + o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2>' + elm + '>'); + o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2>' + elm + '>'); + } else + o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0' + elm + '>'); + }; + + 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; + + // Force root blocks when typing and when getting output + if (s.forced_root_block) { + 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, s = ed.selection; + + if (e.keyCode == 13 && s.getNode().nodeName != 'LI') { + s.setContent(' ', {format : 'raw'}); + n = ed.dom.get('__'); + n.removeAttribute('id'); + s.select(n); + s.collapse(); + return Event.cancel(e); + } + }); + } + + return; + } + + if (!isIE && s.force_p_newlines) { +/* ed.onPreProcess.add(function(ed, o) { + each(ed.dom.select('br', o.node), function(n) { + var p = n.parentNode; + + // Replace with + if (p && p.nodeName == 'p' && (p.childNodes.length == 1 || p.lastChild == n)) { + p.replaceChild(ed.getDoc().createTextNode('\u00a0'), n); + } + }); + });*/ + + ed.onKeyPress.add(function(ed, e) { + if (e.keyCode == 13 && !e.shiftKey) { + if (!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); + }); + } + } + + function ren(rn, na) { + var ne = ed.dom.create(na); + + each(rn.attributes, function(a) { + if (a.specified && a.nodeValue) + ne.setAttribute(a.nodeName.toLowerCase(), a.nodeValue); + }); + + each(rn.childNodes, function(n) { + ne.appendChild(n.cloneNode(true)); + }); + + rn.parentNode.replaceChild(ne, rn); + + return ne; + }; + + // Padd empty inline elements within block elements + // For example: becomes + ed.onPreProcess.add(function(ed, o) { + each(ed.dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) { + if (isEmpty(p)) { + each(ed.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 = ed.selection.getNode().nodeName; + }); + + ed.onKeyUp.add(function(ed, e) { + var bl, sel = ed.selection, n = sel.getNode(), b = ed.getBody(); + + if (b.childNodes.length === 1 && n.nodeName == 'P') { + n = ren(n, s.element); + sel.select(n); + sel.collapse(); + ed.nodeChanged(); + } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') { + bl = ed.dom.getParent(n, 'p'); + + if (bl) { + ren(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]; + + // Is text or non block element + if (nx.nodeType == 3 || (!t.dom.isBlock(nx) && nx.nodeType != 8)) { + 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; + } + } + + bl = ed.dom.create(ed.settings.forced_root_block); + bl.appendChild(nx.cloneNode(1)); + nx.parentNode.replaceChild(bl, 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 (t.dom.getParent(sb, 'ol,ul,pre')) + 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) && sn.nodeValue && so == sn.nodeValue.length) + 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(), 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); diff --git a/js/tiny_mce/classes/Popup.js b/js/tiny_mce/classes/Popup.js new file mode 100644 index 0000000000..71662ece23 --- /dev/null +++ b/js/tiny_mce/classes/Popup.js @@ -0,0 +1,412 @@ +/** + * $Id: Popup.js 1150 2009-06-01 11:50:46Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +// Some global instances +var tinymce = null, tinyMCEPopup, tinyMCE; + +/**#@+ + * @class 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 + * @member tinyMCEPopup + */ +tinyMCEPopup = { + /**#@+ + * @method + */ + + /** + * Initializes the popup this will be called automatically. + */ + 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. + * + * @return {Window} Reference to the parent window that opened the dialog. + */ + getWin : function() { + return window.dialogArguments || opener || parent || top; + }, + + /** + * Returns a window argument/parameter by name. + * + * @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. + * + * @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. + * + * @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. + * + * @param {String} cmd Command to execute. + * @param {bool} 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. + */ + resizeToInnerSize : function() { + var t = this, n, b = document.body, vp = t.dom.getViewPort(window), dw, dh; + + dw = t.getWindowArg('mce_width') - vp.w; + dh = t.getWindowArg('mce_height') - vp.h; + + if (t.isWindow) + window.resizeBy(dw, dh); + else + t.editor.windowManager.resizeBy(dw, dh, t.id); + }, + + /** + * Will executed the specified string when the page has been loaded. This function + * was added for compatibility with the 2.x branch. + * + * @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. + */ + 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. + */ + 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. + */ + 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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + */ + 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 diff --git a/js/tiny_mce/classes/UndoManager.js b/js/tiny_mce/classes/UndoManager.js new file mode 100644 index 0000000000..2a94c7a0ae --- /dev/null +++ b/js/tiny_mce/classes/UndoManager.js @@ -0,0 +1,183 @@ +/** + * $Id: UndoManager.js 1045 2009-03-04 20:03:18Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + /**#@+ + * @class 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. + * @member tinymce.UndoManager + */ + tinymce.create('tinymce.UndoManager', { + index : 0, + data : null, + typing : 0, + + /** + * Constructs a new UndoManager instance. + * + * @constructor + * @param {tinymce.Editor} ed Editor instance to undo/redo in. + */ + UndoManager : function(ed) { + var t = this, Dispatcher = tinymce.util.Dispatcher; + + t.editor = ed; + t.data = []; + t.onAdd = new Dispatcher(this); + t.onUndo = new Dispatcher(this); + t.onRedo = new Dispatcher(this); + }, + + /**#@+ + * @method + */ + + /** + * Adds a new undo level/snapshot to the undo list. + * + * @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(l) { + var t = this, i, ed = t.editor, b, s = ed.settings, la; + + l = l || {}; + l.content = l.content || ed.getContent({format : 'raw', no_events : 1}); + + // Add undo level if needed + l.content = l.content.replace(/^\s*|\s*$/g, ''); + la = t.data[t.index > 0 && (t.index == 0 || t.index == t.data.length) ? t.index - 1 : t.index]; + if (!l.initial && la && l.content == la.content) + return null; + + // Time to compress + if (s.custom_undo_redo_levels) { + if (t.data.length > s.custom_undo_redo_levels) { + for (i = 0; i < t.data.length - 1; i++) + t.data[i] = t.data[i + 1]; + + t.data.length--; + t.index = t.data.length; + } + } + + if (s.custom_undo_redo_restore_selection && !l.initial) + l.bookmark = b = l.bookmark || ed.selection.getBookmark(); + + if (t.index < t.data.length) + t.index++; + + // Only initial marked undo levels should be allowed as first item + // This to workaround a bug with Firefox and the blur event + if (t.data.length === 0 && !l.initial) + return null; + + // Add level + t.data.length = t.index + 1; + t.data[t.index++] = l; + + if (l.initial) + t.index = 0; + + // Set initial bookmark use first real undo level + if (t.data.length == 2 && t.data[0].initial) + t.data[0].bookmark = b; + + t.onAdd.dispatch(t, l); + ed.isNotDirty = 0; + + //console.dir(t.data); + + return l; + }, + + /** + * Undoes the last action. + * + * @return {Object} Undo level or null if no undo was performed. + */ + undo : function() { + var t = this, ed = t.editor, l = l, i; + + if (t.typing) { + t.add(); + t.typing = 0; + } + + if (t.index > 0) { + // If undo on last index then take snapshot + if (t.index == t.data.length && t.index > 1) { + i = t.index; + t.typing = 0; + + if (!t.add()) + t.index = i; + + --t.index; + } + + l = t.data[--t.index]; + ed.setContent(l.content, {format : 'raw'}); + ed.selection.moveToBookmark(l.bookmark); + + t.onUndo.dispatch(t, l); + } + + return l; + }, + + /** + * Redoes the last action. + * + * @return {Object} Redo level or null if no redo was performed. + */ + redo : function() { + var t = this, ed = t.editor, l = null; + + if (t.index < t.data.length - 1) { + l = t.data[++t.index]; + ed.setContent(l.content, {format : 'raw'}); + ed.selection.moveToBookmark(l.bookmark); + + t.onRedo.dispatch(t, l); + } + + return l; + }, + + /** + * Removes all undo levels. + */ + clear : function() { + var t = this; + + t.data = []; + t.index = 0; + t.typing = 0; + t.add({initial : true}); + }, + + /** + * Returns true/false if the undo manager has any undo levels. + * + * @return {bool} true/false if the undo manager has any undo levels. + */ + hasUndo : function() { + return this.index != 0 || this.typing; + }, + + /** + * Returns true/false if the undo manager has any redo levels. + * + * @return {bool} true/false if the undo manager has any redo levels. + */ + hasRedo : function() { + return this.index < this.data.length - 1; + } + + /**#@-*/ + }); +})(tinymce); diff --git a/js/tiny_mce/classes/WindowManager.js b/js/tiny_mce/classes/WindowManager.js new file mode 100644 index 0000000000..eed80232e5 --- /dev/null +++ b/js/tiny_mce/classes/WindowManager.js @@ -0,0 +1,169 @@ +/** + * $Id: WindowManager.js 1045 2009-03-04 20:03:18Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; + + /**#@+ + * @class This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs. + * @member tinymce.WindowManager + */ + tinymce.create('tinymce.WindowManager', { + /** + * Constructs a new window manager instance. + * + * @constructor + * @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 = {}; + }, + + /**#@+ + * @method + */ + + /** + * Opens a new window. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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); + }, + + // 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 new file mode 100644 index 0000000000..e5498bb081 --- /dev/null +++ b/js/tiny_mce/classes/adapter/jquery/adapter.js @@ -0,0 +1,240 @@ +/** + * $Id: adapter.js 1167 2009-06-29 13:07:20Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + * + * This file contains all adapter logic needed to use jQuery as the base API for TinyMCE. + */ + +// #ifdef jquery_adapter + +(function($, tinymce) { + var is = tinymce.is; + + if (!window.jQuery) + return alert("Load jQuery first!"); + + // 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 || []);}, + 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]); + }; +})(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 new file mode 100644 index 0000000000..c315137651 --- /dev/null +++ b/js/tiny_mce/classes/adapter/jquery/jquery.tinymce.js @@ -0,0 +1,179 @@ +/** + * $Id: jquery.uri.js 453 2008-10-14 12:24:41Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function($) { + var lazyLoading, delayedInits = []; + + function patch(type, name, patch_func) { + var func; + + func = $.fn[name]; + + $.fn[name] = function() { + var val; + + if (type !== 'after') { + val = patch_func.apply(this, arguments); + + // We got a return value pass out that instead + if (val !== undefined) + return val; + } + + val = func.apply(this, arguments); + + if (type !== 'before') + patch_func.apply(this, arguments); + + return val; + }; + }; + + $.fn.tinymce = function(settings) { + var t = this, url, suffix = '', ed; + + // No match then just ignore the call + if (!t.length) + return; + + // Get editor instance + if (!settings) + return tinyMCE.get(this[0].id); + + function init() { + // Apply patches once + if (applyPatch) { + applyPatch(); + applyPatch = null; + } + + // Create an editor instance for each matched node + t.each(function(i, n) { + var ed, id = n.id || tinymce.DOM.uniqueId(); + + n.id = id; + ed = new tinymce.Editor(id, settings); + + ed.render(); + }); + }; + + // Load TinyMCE on demand + if (!window['tinymce'] && !lazyLoading && (url = settings.script_url)) { + lazyLoading = 1; + + if (/_(src|dev)\.js/g.test(url)) + suffix = '_src'; + + window.tinyMCEPreInit = { + base : url.substring(0, url.lastIndexOf('/')), + suffix : suffix, + query : '' + }; + + $.getScript(url, function() { + // Script is loaded time to initialize TinyMCE + tinymce.dom.Event.domLoaded = 1; + lazyLoading = 2; + init(); + + $.each(delayedInits, function(i, init) { + init(); + }); + }); + } else { + if (lazyLoading === 1) + delayedInits.push(init); + else + init(); + } + }; + + // Add :tinymce psuedo selector + $.extend($.expr[':'], { + tinymce : function(e) { + return e.id && !!tinyMCE.get(e.id); + } + }); + + function applyPatch() { + function removeEditors() { + this.find('span.mceEditor,div.mceEditor').each(function(i, n) { + var ed; + + if (ed = tinyMCE.get(n.id.replace(/_parent$/, ''))) { + ed.remove(); + } + }); + }; + + function loadOrSave(value) { + var ed; + + // Handle set value + if (value !== undefined) { + removeEditors.call(this); + + // Saves the contents before get/set value of textarea/div + this.each(function(i, node) { + var ed; + + if (ed = tinyMCE.get(node.id)) + ed.setContent(value); + }); + } else if (this.length > 0) { + // Handle get value + if (ed = tinyMCE.get(this[0].id)) + return ed.getContent(); + } + }; + + // Patch various jQuery functions + patch("both", 'text', function(value) { + // Text encode value + if (value !== undefined) + return loadOrSave.call(this, value); + + // Get contents as plain text + if (this.length > 0) { + // Handle get value + if (ed = tinyMCE.get(this[0].id)) + return ed.getContent().replace(/<[^>]+>/g, ''); + } + }); + + $.each(['val', 'html'], function(i, name) { + patch("both", name, loadOrSave); + }); + + $.each(['append', 'prepend'], function(i, name) { + patch("before", name, function(value) { + if (value !== undefined) { + this.each(function(i, node) { + var ed; + + if (ed = tinyMCE.get(node.id)) { + if (name === 'append') + ed.setContent(ed.getContent() + value); + else + ed.setContent(value + ed.getContent()); + } + }); + } + }); + }); + + patch("both", 'attr', function(name, value) { + if (name && name === 'value') + return loadOrSave.call(this, value); + }); + + $.each(['remove', 'replaceWith', 'replaceAll', 'empty'], function(i, name) { + patch("before", name, removeEditors); + }); + }; +})(jQuery); \ No newline at end of file diff --git a/js/tiny_mce/classes/adapter/prototype/adapter.js b/js/tiny_mce/classes/adapter/prototype/adapter.js new file mode 100644 index 0000000000..51822dfaf8 --- /dev/null +++ b/js/tiny_mce/classes/adapter/prototype/adapter.js @@ -0,0 +1,38 @@ +/** + * $Id: adapter.js 1000 2009-02-12 13:03:40Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + * + * This file contains all adapter logic needed to use prototype library as the base API for TinyMCE. + */ + +// #ifdef prototype_adapter + +(function() { + if (!window.Prototype) + return alert("Load prototype first!"); + + // Patch in core NS functions + tinymce.extend(tinymce, { + trim : function(s) {return s ? s.strip() : '';}, + inArray : function(a, v) {return a && a.indexOf ? a.indexOf(v) : -1;} + }); + + // Patch in functions in various clases + // Add a "#ifndefjquery" statement around each core API function you add below + var patches = { + 'tinymce.util.JSON' : { + serialize : function(o) { + return o.toJSON(); + } + }, + }; + + // Patch functions after a class is created + tinymce.onCreate = function(ty, c, p) { + tinymce.extend(p, patches[c]); + }; +})(); + +// #endif diff --git a/js/tiny_mce/classes/commands/BlockQuote.js b/js/tiny_mce/classes/commands/BlockQuote.js new file mode 100644 index 0000000000..44ef9b1243 --- /dev/null +++ b/js/tiny_mce/classes/commands/BlockQuote.js @@ -0,0 +1,135 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + tinymce.GlobalCommands.add('mceBlockQuote', function() { + var ed = this, s = ed.selection, dom = ed.dom, sb, eb, n, bm, bq, r, bq2, i, nl; + + function getBQ(e) { + return dom.getParent(e, function(n) {return n.nodeName === 'BLOCKQUOTE';}); + }; + + // Get start/end block + sb = dom.getParent(s.getStart(), dom.isBlock); + eb = dom.getParent(s.getEnd(), dom.isBlock); + + // Remove blockquote(s) + if (bq = getBQ(sb)) { + if (sb != eb || sb.childNodes.length > 1 || (sb.childNodes.length == 1 && sb.firstChild.nodeName != 'BR')) + bm = s.getBookmark(); + + // Move all elements after the end block into new bq + if (getBQ(eb)) { + bq2 = bq.cloneNode(false); + + while (n = eb.nextSibling) + bq2.appendChild(n.parentNode.removeChild(n)); + } + + // Add new bq after + if (bq2) + dom.insertAfter(bq2, bq); + + // Move all selected blocks after the current bq + nl = s.getSelectedBlocks(sb, eb); + for (i = nl.length - 1; i >= 0; i--) { + dom.insertAfter(nl[i], bq); + } + + // Empty bq, then remove it + if (/^\s*$/.test(bq.innerHTML)) + dom.remove(bq, 1); // Keep children so boomark restoration works correctly + + // Empty bq, then remote it + if (bq2 && /^\s*$/.test(bq2.innerHTML)) + dom.remove(bq2, 1); // Keep children so boomark restoration works correctly + + if (!bm) { + // Move caret inside empty block element + if (!tinymce.isIE) { + r = ed.getDoc().createRange(); + r.setStart(sb, 0); + r.setEnd(sb, 0); + s.setRng(r); + } else { + s.select(sb); + s.collapse(0); + + // IE misses the empty block some times element so we must move back the caret + if (dom.getParent(s.getStart(), dom.isBlock) != sb) { + r = s.getRng(); + r.move('character', -1); + r.select(); + } + } + } else + ed.selection.moveToBookmark(bm); + + return; + } + + // Since IE can start with a totally empty document we need to add the first bq and paragraph + if (tinymce.isIE && !sb && !eb) { + ed.getDoc().execCommand('Indent'); + n = getBQ(s.getNode()); + n.style.margin = n.dir = ''; // IE adds margin and dir to bq + return; + } + + if (!sb || !eb) + return; + + // If empty paragraph node then do not use bookmark + if (sb != eb || sb.childNodes.length > 1 || (sb.childNodes.length == 1 && sb.firstChild.nodeName != 'BR')) + bm = s.getBookmark(); + + // Move selected block elements into a bq + tinymce.each(s.getSelectedBlocks(getBQ(s.getStart()), getBQ(s.getEnd())), function(e) { + // Found existing BQ add to this one + if (e.nodeName == 'BLOCKQUOTE' && !bq) { + bq = e; + return; + } + + // No BQ found, create one + if (!bq) { + bq = dom.create('blockquote'); + e.parentNode.insertBefore(bq, e); + } + + // Add children from existing BQ + if (e.nodeName == 'BLOCKQUOTE' && bq) { + n = e.firstChild; + + while (n) { + bq.appendChild(n.cloneNode(true)); + n = n.nextSibling; + } + + dom.remove(e); + return; + } + + // Add non BQ element to BQ + bq.appendChild(dom.remove(e)); + }); + + if (!bm) { + // Move caret inside empty block element + if (!tinymce.isIE) { + r = ed.getDoc().createRange(); + r.setStart(sb, 0); + r.setEnd(sb, 0); + s.setRng(r); + } else { + s.select(sb); + s.collapse(1); + } + } else + s.moveToBookmark(bm); + }); +})(tinymce); diff --git a/js/tiny_mce/classes/commands/CutCopyPaste.js b/js/tiny_mce/classes/commands/CutCopyPaste.js new file mode 100644 index 0000000000..886842ddc1 --- /dev/null +++ b/js/tiny_mce/classes/commands/CutCopyPaste.js @@ -0,0 +1,24 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + tinymce.each(['Cut', 'Copy', 'Paste'], function(cmd) { + tinymce.GlobalCommands.add(cmd, function() { + var ed = this, doc = ed.getDoc(); + + try { + doc.execCommand(cmd, false, null); + + // On WebKit the command will just be ignored if it's not enabled + if (!doc.queryCommandSupported(cmd)) + throw 'Error'; + } catch (ex) { + ed.windowManager.alert(ed.getLang('clipboard_no_support')); + } + }); + }); +})(tinymce); diff --git a/js/tiny_mce/classes/commands/InsertHorizontalRule.js b/js/tiny_mce/classes/commands/InsertHorizontalRule.js new file mode 100644 index 0000000000..02542aa85f --- /dev/null +++ b/js/tiny_mce/classes/commands/InsertHorizontalRule.js @@ -0,0 +1,15 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + tinymce.GlobalCommands.add('InsertHorizontalRule', function() { + if (tinymce.isOpera) + return this.getDoc().execCommand('InsertHorizontalRule', false, ''); + + this.selection.setContent(''); + }); +})(tinymce); diff --git a/js/tiny_mce/classes/commands/RemoveFormat.js b/js/tiny_mce/classes/commands/RemoveFormat.js new file mode 100644 index 0000000000..6a43902f5c --- /dev/null +++ b/js/tiny_mce/classes/commands/RemoveFormat.js @@ -0,0 +1,173 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + function processRange(dom, start, end, callback) { + var ancestor, n, startPoint, endPoint, sib; + + function findEndPoint(n, c) { + do { + if (n.parentNode == c) + return n; + + n = n.parentNode; + } while(n); + }; + + function process(n) { + callback(n); + tinymce.walk(n, callback, 'childNodes'); + }; + + // Find common ancestor and end points + ancestor = dom.findCommonAncestor(start, end); + startPoint = findEndPoint(start, ancestor) || start; + endPoint = findEndPoint(end, ancestor) || end; + + // Process left leaf + for (n = start; n && n != startPoint; n = n.parentNode) { + for (sib = n.nextSibling; sib; sib = sib.nextSibling) + process(sib); + } + + // Process middle from start to end point + if (startPoint != endPoint) { + for (n = startPoint.nextSibling; n && n != endPoint; n = n.nextSibling) + process(n); + } else + process(startPoint); + + // Process right leaf + for (n = end; n && n != endPoint; n = n.parentNode) { + for (sib = n.previousSibling; sib; sib = sib.previousSibling) + process(sib); + } + }; + + tinymce.GlobalCommands.add('RemoveFormat', function() { + var ed = this, dom = ed.dom, s = ed.selection, r = s.getRng(1), nodes = [], bm, start, end, sc, so, ec, eo, n; + + function findFormatRoot(n) { + var sp; + + dom.getParent(n, function(n) { + if (dom.is(n, ed.getParam('removeformat_selector'))) + sp = n; + + return dom.isBlock(n); + }, ed.getBody()); + + return sp; + }; + + function collect(n) { + if (dom.is(n, ed.getParam('removeformat_selector'))) + nodes.push(n); + }; + + function walk(n) { + collect(n); + tinymce.walk(n, collect, 'childNodes'); + }; + + bm = s.getBookmark(); + sc = r.startContainer; + ec = r.endContainer; + so = r.startOffset; + eo = r.endOffset; + sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc; + ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec; + + // Same container + if (sc == ec) { // TEXT_NODE + start = findFormatRoot(sc); + + // Handle single text node + if (sc.nodeType == 3) { + if (start && start.nodeType == 1) { // ELEMENT + n = sc.splitText(so); + n.splitText(eo - so); + dom.split(start, n); + + s.moveToBookmark(bm); + } + + return; + } + + // Handle single element + walk(dom.split(start, sc) || sc); + } else { + // Find start/end format root + start = findFormatRoot(sc); + end = findFormatRoot(ec); + + // Split start text node + if (start) { + if (sc.nodeType == 3) { // TEXT + // Since IE doesn't support white space nodes in the DOM we need to + // add this invisible character so that the splitText function can split the contents + if (so == sc.nodeValue.length) + sc.nodeValue += '\uFEFF'; // Yet another pesky IE fix + + sc = sc.splitText(so); + } + } + + // Split end text node + if (end) { + if (ec.nodeType == 3) // TEXT + ec.splitText(eo); + } + + // If the start and end format root is the same then we need to wrap + // the end node in a span since the split calls might change the reference + // Example: x[yz---12]3 + if (start && start == end) + dom.replace(dom.create('span', {id : '__end'}, ec.cloneNode(true)), ec); + + // Split all start containers down to the format root + if (start) + start = dom.split(start, sc); + else + start = sc; + + // If there is a span wrapper use that one instead + if (n = dom.get('__end')) { + ec = n; + end = findFormatRoot(ec); + } + + // Split all end containers down to the format root + if (end) + end = dom.split(end, ec); + else + end = ec; + + // Collect nodes in between + processRange(dom, start, end, collect); + + // Remove invisible character for IE workaround if we find it + if (sc.nodeValue == '\uFEFF') + sc.nodeValue = ''; + + // Process start/end container elements + walk(ec); + walk(sc); + } + + // Remove all collected nodes + tinymce.each(nodes, function(n) { + dom.remove(n, 1); + }); + + // Remove leftover wrapper + dom.remove('__end', 1); + + s.moveToBookmark(bm); + }); +})(tinymce); diff --git a/js/tiny_mce/classes/commands/UndoRedo.js b/js/tiny_mce/classes/commands/UndoRedo.js new file mode 100644 index 0000000000..b2745e1e38 --- /dev/null +++ b/js/tiny_mce/classes/commands/UndoRedo.js @@ -0,0 +1,38 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function() { + var cmds = tinymce.GlobalCommands; + + cmds.add(['mceEndUndoLevel', 'mceAddUndoLevel'], function() { + this.undoManager.add(); + }); + + cmds.add('Undo', function() { + var ed = this; + + if (ed.settings.custom_undo_redo) { + ed.undoManager.undo(); + ed.nodeChanged(); + return true; + } + + return false; // Run browser command + }); + + cmds.add('Redo', function() { + var ed = this; + + if (ed.settings.custom_undo_redo) { + ed.undoManager.redo(); + ed.nodeChanged(); + return true; + } + + return false; // Run browser command + }); +})(); diff --git a/js/tiny_mce/classes/dom/DOMUtils.js b/js/tiny_mce/classes/dom/DOMUtils.js new file mode 100644 index 0000000000..8f07a4a297 --- /dev/null +++ b/js/tiny_mce/classes/dom/DOMUtils.js @@ -0,0 +1,1823 @@ +/** + * $Id: DOMUtils.js 1154 2009-06-10 17:31:03Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + // Shorten names + var each = tinymce.each, is = tinymce.is; + var isWebKit = tinymce.isWebKit, isIE = tinymce.isIE; + + /**#@+ + * @class Utility class for various DOM manipulation and retrival functions. + * @member 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 + * @param {Document} d Document reference to bind the utility class to. + * @param {settings} s Optional settings collection. + */ + DOMUtils : function(d, s) { + var t = this; + + 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; + } + } + + tinymce.addUnload(t.destroy, t); + }, + + /**#@+ + * @method + */ + + /** + * 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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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; + }, + + // #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. + * + * @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. + * + * @param {Node/NodeList} n DOM node to match or an array of nodes to match. + * @param {String} patt CSS pattern to match the element agains. + */ + is : function(n, patt) { + return tinymce.dom.Sizzle.matches(patt, n.nodeType ? [n] : n).length > 0; + }, + + // #endif + + /** + * Adds the specified element to another element or elements. + * + * @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 {bool} 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. + * + * @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. + * + * @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 + '' + n + '>'; + + return o + ' />'; + }, + + /** + * Removes/deletes the specified element(s) from the DOM. + * + * @param {String/Element/Array} n ID of element or DOM element object or array containing multiple elements/ids. + * @param {bool} k 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(n, k) { + var t = this; + + return this.run(n, function(n) { + var p, g, i; + + p = n.parentNode; + + if (!p) + return null; + + if (k) { + for (i = n.childNodes.length - 1; i >= 0; i--) + t.insertAfter(n.childNodes[i], n); + + //each(n.childNodes, function(c) { + // p.insertBefore(c.cloneNode(true), n); + //}); + } + + // Fix IE psuedo leak + if (t.fixPsuedoLeaks) { + p = n.cloneNode(true); + k = 'IELeakGarbageBin'; + g = t.get(k) || t.add(t.doc.body, 'div', {id : k, style : 'display:none'}); + g.appendChild(n); + g.innerHTML = ''; + + return p; + } + + return p.removeChild(n); + }); + }, + + /** + * Sets the CSS style value on a HTML element. The name can be a camelcase string + * or the CSS style name like background-color. + * + * @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. + * + * @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). + * + * @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. + * + * @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. + * + * @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. + * + * @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); + + if (n === 'style') { + v = v || e.style.cssText; + + if (v) { + v = t.serializeStyle(t.parseStyle(v)); + + 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. + * + * @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. + * + * @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. + * + * @param {Object} o Object to serialize as string for example: {border : '1px solid red'} + * @return {String} String representation of the style object for example: border: 1px solid red. + */ + serializeStyle : function(o) { + var s = ''; + + each(o, function(v, k) { + if (k && v) { + if (tinymce.isGecko && k.indexOf('-moz-') === 0) + return; + + switch (k) { + case 'color': + case 'background-color': + v = v.toLowerCase(); + break; + } + + s += (s ? ' ' : '') + k + ': ' + v + ';'; + } + }); + + return s; + }, + + /** + * Imports/loads the specified CSS file into the document bound to the class. + * + * @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. + * + * @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. + * + * @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, ' '); + + return e.className = tinymce.trim(v != ' ' ? v : ''); + } + + return e.className; + }); + }, + + /** + * Returns true if the specified element has the specified class. + * + * @param {String/Element} n HTML element or element id string to check CSS class on. + * @param {String] c CSS class to check for. + * @return {bool} 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. + * + * @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. + * + * @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. + * + * @param {String/Element} e Id or element to check display state on. + * @return {bool} 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. + * + * @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. + * + * @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() { + 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 + + // Remove all child nodes + while (e.firstChild) + e.firstChild.removeNode(); + + // 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(/]+)>|/g, ''); + h = h.replace(/<\/p>/g, ''); + + // Set the new HTML with DIVs + set(); + + // Replace all DIV elements with he 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, '<$1b$2>'); + h = h.replace(/<(\/?)em>|]+)>/gi, '<$1i$2>'); + } else if (isIE) { + h = h.replace(/'/g, '''); // IE can't handle apos + h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct + } + + // Fix some issues + h = h.replace(/]+)\/>|/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 (/'); + }; + + if (!tinymce.is(u, 'string')) { + each(u, function(u) { + loadScript(u); + }); + + if (cb) + cb.call(s || t); + } else { + loadScript(u); + + if (cb) + cb.call(s || t); + } + }, + + /** + * Starts the loading of the queue. + * + * @param {function} cb Optional callback to execute when all queued items are loaded. + * @param {Object} s Optional scope to execute the callback in. + */ + loadQueue : function(cb, s) { + var t = this; + + if (!t.queueLoading) { + t.queueLoading = 1; + t.queueCallbacks = []; + + t.loadScripts(t.queue, function() { + t.queueLoading = 0; + + if (cb) + cb.call(s || t); + + each(t.queueCallbacks, function(o) { + o.func.call(o.scope); + }); + }); + } else if (cb) + t.queueCallbacks.push({func : cb, scope : s || t}); + }, + + /** + * Evaluates the specified string inside the global namespace/window scope. + * + * @param {string} Script contents to evaluate. + */ + eval : function(co) { + var w = window; + + // Evaluate script + if (!w.execScript) { + try { + eval.call(w, co); + } catch (ex) { + eval(co, w); // Firefox 3.0a8 + } + } else + w.execScript(co); // IE + }, + + /** + * 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. + * + * @param {Array} sc Array of queue items to load. + * @param {function} cb Optional callback to execute ones all items are loaded. + * @param {Object} s Optional scope to execute callback in. + */ + loadScripts : function(sc, cb, s) { + var t = this, lo = t.lookup; + + function done(o) { + o.state = 2; // Has been loaded + + // Run callback + if (o.func) + o.func.call(o.scope || t); + }; + + function allDone() { + var l; + + // Check if all files are loaded + l = sc.length; + each(sc, function(o) { + o = lo[o.url]; + + if (o.state === 2) {// It has finished loading + done(o); + l--; + } else + load(o); + }); + + // They are all loaded + if (l === 0 && cb) { + cb.call(s || t); + cb = 0; + } + }; + + function load(o) { + if (o.state > 0) + return; + + o.state = 1; // Is loading + + tinymce.dom.ScriptLoader.loadScript(o.url, function() { + done(o); + allDone(); + }); + + /* + tinymce.util.XHR.send({ + url : o.url, + error : t.settings.error, + success : function(co) { + t.eval(co); + done(o); + allDone(); + } + }); + */ + }; + + each(sc, function(o) { + var u = o.url; + + // Add to queue if needed + if (!lo[u]) { + lo[u] = o; + t.queue.push(o); + } else + o = lo[u]; + + // Is already loading or has been loaded + if (o.state > 0) + return; + + if (!Event.domLoaded && !t.settings.strict_mode) { + var ix, ol = ''; + + // Add onload events + if (cb || o.func) { + o.state = 1; // Is loading + + ix = tinymce.dom.ScriptLoader._addOnLoad(function() { + done(o); + allDone(); + }); + + if (tinymce.isIE) + ol = ' onreadystatechange="'; + else + ol = ' onload="'; + + ol += 'tinymce.dom.ScriptLoader._onLoad(this,\'' + u + '\',' + ix + ');"'; + } + + document.write(''); + + if (!o.func) + done(o); + } else + load(o); + }); + + allDone(); + }, + + // Static methods + 'static' : { + _addOnLoad : function(f) { + var t = this; + + t._funcs = t._funcs || []; + t._funcs.push(f); + + return t._funcs.length - 1; + }, + + _onLoad : function(e, u, ix) { + if (!tinymce.isIE || e.readyState == 'complete') + this._funcs[ix].call(this); + }, + + /** + * Loads the specified script without adding it to any load queue. + * + * @param {string} u URL to dynamically load. + * @param {function} cb Callback function to executed on load. + */ + loadScript : function(u, cb) { + var id = tinymce.DOM.uniqueId(), e; + + function done() { + Event.clear(id); + tinymce.DOM.remove(id); + + if (cb) { + cb.call(document, u); + cb = 0; + } + }; + + if (tinymce.isIE) { +/* Event.add(e, 'readystatechange', function(e) { + if (e.target && e.target.readyState == 'complete') + done(); + });*/ + + tinymce.util.XHR.send({ + url : tinymce._addVer(u), + async : false, + success : function(co) { + window.execScript(co); + done(); + } + }); + } else { + e = tinymce.DOM.create('script', {id : id, type : 'text/javascript', src : tinymce._addVer(u)}); + Event.add(e, 'load', done); + + // Check for head or body + (document.getElementsByTagName('head')[0] || document.body).appendChild(e); + } + } + } + + /**#@-*/ + }); + + // 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 new file mode 100644 index 0000000000..87c0a76fda --- /dev/null +++ b/js/tiny_mce/classes/dom/Selection.js @@ -0,0 +1,747 @@ +/** + * $Id: Selection.js 1149 2009-06-01 11:47:08Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + function trimNl(s) { + return s.replace(/[\n\r]+/g, ''); + }; + + // Shorten names + var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; + + /**#@+ + * @class 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. + * @member tinymce.dom.Selection + */ + tinymce.create('tinymce.dom.Selection', { + /** + * Constructs a new selection instance. + * + * @constructor + * @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); + }, + + /**#@+ + * @method + */ + + /** + * Returns the selected contents using the DOM serializer passed in to this class. + * + * @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. + * + * @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 + 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.setEndAfter(c); + t.setRng(r); + + // Delete the marker, and hopefully the caret gets placed in the right location + // Removed this since it seems to remove in FF and simply deleting it + // doesn't seem to affect the caret position in any browser + //d.execCommand('Delete', false, null); + + // 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. + * + * @return {Element} Start element of selection range. + */ + getStart : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(1); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.firstChild; + + return e; + } else { + e = r.startContainer; + + if (e.nodeName == 'BODY') + return e.firstChild; + + return t.dom.getParent(e, '*'); + } + }, + + /** + * Returns the end element of a selection range. If the end is in a text + * node the parent element will be returned. + * + * @return {Element} End element of selection range. + */ + getEnd : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild; + + return e; + } else { + e = r.endContainer; + + if (e.nodeName == 'BODY') + return e.lastChild; + + return t.dom.getParent(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. + * + * @param {bool} si Optional state if the bookmark should be simple or not. Default is complex. + * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection. + */ + getBookmark : function(si) { + var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv; + sx = vp.x; + sy = vp.y; + + // Simple bookmark fast but not as persistent + if (si) + return {rng : r, scrollX : sx, scrollY : sy}; + + // Handle IE + if (isIE) { + // Control selection + if (r.item) { + e = r.item(0); + + each(t.dom.select(e.nodeName), function(n, i) { + if (e == n) { + sp = i; + return false; + } + }); + + return { + tag : e.nodeName, + index : sp, + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + tr = t.dom.doc.body.createTextRange(); + tr.moveToElementText(ro); + tr.collapse(true); + bp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(true); + sp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(false); + le = Math.abs(tr.move('character', c)) - sp; + + return { + start : sp - bp, + length : le, + scrollX : sx, + scrollY : sy + }; + } + + // Handle W3C + e = t.getNode(); + s = t.getSel(); + + if (!s) + return null; + + // Image selection + if (e && e.nodeName == 'IMG') { + return { + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + + function getPos(r, sn, en) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; + + while ((n = w.nextNode()) != null) { + if (n == sn) + d.start = p; + + if (n == en) { + d.end = p; + return d; + } + + p += trimNl(n.nodeValue || '').length; + } + + return null; + }; + + // Caret or selection + if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) { + e = getPos(ro, s.anchorNode, s.focusNode); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + // Count whitespace before + trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + + return { + start : Math.max(e.start + s.anchorOffset - wb, 0), + end : Math.max(e.end + s.focusOffset - wb, 0), + scrollX : sx, + scrollY : sy, + beg : s.anchorOffset - wb == 0 + }; + } else { + e = getPos(ro, r.startContainer, r.endContainer); + + // Count whitespace before start and end container + //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;}); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + return { + start : Math.max(e.start + r.startOffset - wb, 0), + end : Math.max(e.end + r.endOffset - wa, 0), + scrollX : sx, + scrollY : sy, + beg : r.startOffset - wb == 0 + }; + } + }, + + /** + * Restores the selection to the specified bookmark. + * + * @param {Object} bookmark Bookmark to restore selection from. + * @return {bool} true/false if it was successful or not. + */ + moveToBookmark : function(b) { + var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv; + + function getPos(r, sp, ep) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb; + + while ((n = w.nextNode()) != null) { + wa = wb = 0; + + nv = n.nodeValue || ''; + //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;}); + //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;}); + + nvl = trimNl(nv).length; + p += nvl; + + if (p >= sp && !d.startNode) { + o = sp - (p - nvl); + + // Fix for odd quirk in FF + if (b.beg && o >= nvl) + continue; + + d.startNode = n; + d.startOffset = o + wb; + } + + if (p >= ep) { + d.endNode = n; + d.endOffset = ep - (p - nvl) + wb; + return d; + } + } + + return null; + }; + + if (!b) + return false; + + t.win.scrollTo(b.scrollX, b.scrollY); + + // Handle explorer + if (isIE) { + // Handle simple + if (r = b.rng) { + try { + r.select(); + } catch (ex) { + // Ignore + } + + return true; + } + + t.win.focus(); + + // Handle control bookmark + if (b.tag) { + r = ro.createControlRange(); + + each(t.dom.select(b.tag), function(n, i) { + if (i == b.index) + r.addElement(n); + }); + } else { + // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs + try { + // Incorrect bookmark + if (b.start < 0) + return true; + + r = s.createRange(); + r.moveToElementText(ro); + r.collapse(true); + r.moveStart('character', b.start); + r.moveEnd('character', b.length); + } catch (ex2) { + return true; + } + } + + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + + return true; + } + + // Handle W3C + if (!s) + return false; + + // Handle simple + if (b.rng) { + s.removeAllRanges(); + s.addRange(b.rng); + } else { + if (is(b.start) && is(b.end)) { + try { + sd = getPos(ro, b.start, b.end); + + if (sd) { + r = t.dom.doc.createRange(); + r.setStart(sd.startNode, sd.startOffset); + r.setEnd(sd.endNode, sd.endOffset); + s.removeAllRanges(); + s.addRange(r); + } + + if (!tinymce.isOpera) + t.win.focus(); + } catch (ex) { + // Ignore + } + } + } + }, + + /** + * Selects the specified element. This will place the start and end of the selection range around the element. + * + * @param {Element} n HMTL DOM element to select. + * @param {} c 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(n, c) { + var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document; + + function find(n, start) { + var walker, o; + + if (n) { + walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + // Find first/last non empty text node + while (n = walker.nextNode()) { + o = n; + + if (tinymce.trim(n.nodeValue).length != 0) { + if (start) + return n; + else + o = n; + } + } + } + + return o; + }; + + if (isIE) { + try { + b = d.body; + + if (/^(IMG|TABLE)$/.test(n.nodeName)) { + r = b.createControlRange(); + r.addElement(n); + } else { + r = b.createTextRange(); + r.moveToElementText(n); + } + + r.select(); + } catch (ex) { + // Throws illigal agrument in IE some times + } + } else { + if (c) { + fn = find(n, 1) || t.dom.select('br:first', n)[0]; + ln = find(n, 0) || t.dom.select('br:last', n)[0]; + + if (fn && ln) { + r = d.createRange(); + + if (fn.nodeName == 'BR') + r.setStartBefore(fn); + else + r.setStart(fn, 0); + + if (ln.nodeName == 'BR') + r.setEndBefore(ln); + else + r.setEnd(ln, ln.nodeValue.length); + } else + r.selectNode(n); + } else + r.selectNode(n); + + t.setRng(r); + } + + return n; + }, + + /** + * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection. + * + * @return {bool} 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; + + return !s || r.boundingWidth == 0 || r.collapsed; + }, + + /** + * Collapse the selection to start or end of range. + * + * @param {bool} 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. + * + * @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. + * + * @param {bool} 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 = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange(); + + return r; + }, + + /** + * Changes the selection to the specified DOM range. + * + * @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. + * + * @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. + * + * @return {Element} Currently selected element or common ancestor element. + */ + getNode : function() { + var t = this, r = t.getRng(), s = t.getSel(), e; + + if (!isIE) { + // Range maybe lost after the editor is made visible again + if (!r) + return t.dom.getRoot(); + + e = r.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!r.collapsed) { + // If the anchor node is a element instead of a text node then return this element + if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1) + return s.anchorNode.childNodes[s.anchorOffset]; + + if (r.startContainer == r.endContainer) { + if (r.startOffset - r.endOffset < 2) { + if (r.startContainer.hasChildNodes()) + e = r.startContainer.childNodes[r.startOffset]; + } + } + } + + return t.dom.getParent(e, '*'); + } + + return r.item ? r.item(0) : r.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); diff --git a/js/tiny_mce/classes/dom/Serializer.js b/js/tiny_mce/classes/dom/Serializer.js new file mode 100644 index 0000000000..65717ecaa5 --- /dev/null +++ b/js/tiny_mce/classes/dom/Serializer.js @@ -0,0 +1,979 @@ +/** + * $Id: Serializer.js 1173 2009-06-29 14:44:25Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(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'); + }; + + /**#@+ + * @class 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. + * @member tinymce.dom.Serializer + */ + tinymce.create('tinymce.dom.Serializer', { + /** + * Constucts a new DOM serializer class. + * + * @constructor + * @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', + bool_attrs : /(checked|disabled|readonly|selected|nowrap)/, + valid_elements : '*[*]', + extended_valid_elements : 0, + valid_child_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, + font_size_style_values : 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; + + 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 '' + c + '>'; + + 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 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 ]*>)(.*?)(<\/script>)/g}, + {pattern : /(]*>)(.*?)(<\/noscript>)/g}, + {pattern : /("});j=j.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(h,l,k){return""})}j=j.replace(//g,"");j=j.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi,function(h,l){function k(o,n,q){var p=q;if(h.indexOf("mce_"+n)!=-1){return o}if(n=="style"){if(g._isRes(q)){return o}if(i.hex_colors){p=p.replace(/rgb\([^\)]+\)/g,function(m){return g.toHex(m)})}if(i.url_converter){p=p.replace(/url\([\'\"]?([^\)\'\"]+)\)/g,function(m,r){return"url("+g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(r),n,l))+")"})}}else{if(n!="coords"&&n!="shape"){if(i.url_converter){p=g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(q),n,l))}}}return" "+n+'="'+q+'" mce_'+n+'="'+p+'"'}h=h.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi,k);h=h.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi,k);return h.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi,k)})}return j},getOuterHTML:function(f){var g;f=this.get(f);if(!f){return null}if(f.outerHTML!==undefined){return f.outerHTML}g=(f.ownerDocument||this.doc).createElement("body");g.appendChild(f.cloneNode(true));return g.innerHTML},setOuterHTML:function(i,g,j){var f=this;return this.run(i,function(h){var l,k;h=f.get(h);j=j||h.ownerDocument||f.doc;if(a&&h.nodeType==1){h.outerHTML=g}else{k=j.createElement("body");k.innerHTML=g;l=k.lastChild;while(l){f.insertAfter(l.cloneNode(true),h);l=l.previousSibling}f.remove(h)}})},decode:function(g){var h,i,f;if(/&[^;]+;/.test(g)){h=this.doc.createElement("div");h.innerHTML=g;i=h.firstChild;f="";if(i){do{f+=i.nodeValue}while(i.nextSibling)}return f||g}return g},encode:function(f){return f?(""+f).replace(/[<>&\"]/g,function(h,g){switch(h){case"&":return"&";case'"':return""";case"<":return"<";case">":return">"}return h}):f},insertAfter:function(h,g){var f=this;g=f.get(g);return this.run(h,function(k){var j,i;j=g.parentNode;i=g.nextSibling;if(i){j.insertBefore(k,i)}else{j.appendChild(k)}return k})},isBlock:function(f){if(f.nodeType&&f.nodeType!==1){return false}f=f.nodeName||f;return/^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(f)},replace:function(i,h,f){var g=this;if(b(h,"array")){i=i.cloneNode(true)}return g.run(h,function(j){if(f){e(j.childNodes,function(k){i.appendChild(k.cloneNode(true))})}if(g.fixPsuedoLeaks&&j.nodeType===1){j.parentNode.insertBefore(i,j);g.remove(j);return i}return j.parentNode.replaceChild(i,j)})},findCommonAncestor:function(h,f){var i=h,g;while(i){g=f;while(g&&i!=g){g=g.parentNode}if(i==g){break}i=i.parentNode}if(!i&&h.ownerDocument){return h.ownerDocument.documentElement}return i},toHex:function(f){var h=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(f);function g(i){i=parseInt(i).toString(16);return i.length>1?i:"0"+i}if(h){f="#"+g(h[1])+g(h[2])+g(h[3]);return f}return f},getClasses:function(){var l=this,g=[],k,m={},n=l.settings.class_filter,j;if(l.classes){return l.classes}function o(f){e(f.imports,function(i){o(i)});e(f.cssRules||f.rules,function(i){switch(i.type||1){case 1:if(i.selectorText){e(i.selectorText.split(","),function(p){p=p.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(p)||!/\.[\w\-]+$/.test(p)){return}j=p;p=p.replace(/.*\.([a-z0-9_\-]+).*/i,"$1");if(n&&!(p=n(p,j))){return}if(!m[p]){g.push({"class":p});m[p]=1}})}break;case 3:o(i.styleSheet);break}})}try{e(l.doc.styleSheets,o)}catch(h){}if(g.length>0){l.classes=g}return g},run:function(j,i,h){var g=this,k;if(g.doc&&typeof(j)==="string"){j=g.get(j)}if(!j){return false}h=h||this;if(!j.nodeType&&(j.length||j.length===0)){k=[];e(j,function(l,f){if(l){if(typeof(l)=="string"){l=g.doc.getElementById(l)}k.push(i.call(h,l,f))}});return k}return i.call(h,j)},getAttribs:function(g){var f;g=this.get(g);if(!g){return[]}if(a){f=[];if(g.nodeName=="OBJECT"){return g.attributes}g.cloneNode(false).outerHTML.replace(/([a-z0-9\:\-_]+)=/gi,function(i,h){f.push({specified:1,nodeName:h})});return f}return g.attributes},destroy:function(g){var f=this;if(f.events){f.events.destroy()}f.win=f.doc=f.root=f.events=null;if(!g){c.removeUnload(f.destroy)}},createRng:function(){var f=this.doc;return f.createRange?f.createRange():new c.dom.Range(this)},split:function(k,j,n){var o=this,f=o.createRng(),l,i,m;function g(q,p){q=q[p];if(q&&q[p]&&q[p].nodeType==1&&h(q[p])){o.remove(q[p])}}function h(p){p=o.getOuterHTML(p);p=p.replace(/<(img|hr|table)/gi,"-");p=p.replace(/<[^>]+>/g,"");return p.replace(/[ \t\r\n]+| | /g,"")==""}if(k&&j){f.setStartBefore(k);f.setEndBefore(j);l=f.extractContents();f=o.createRng();f.setStartAfter(j);f.setEndAfter(k);i=f.extractContents();m=k.parentNode;g(l,"lastChild");if(!h(l)){m.insertBefore(l,k)}if(n){m.replaceChild(n,j)}else{m.insertBefore(j,k)}g(i,"firstChild");if(!h(i)){m.insertBefore(i,k)}o.remove(k);return n||j}},bind:function(j,f,i,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.add(j,f,i,h||this)},unbind:function(i,f,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.remove(i,f,h)},_isRes:function(f){return/^(top|left|bottom|right|width|height)/i.test(f)||/;\s*(top|left|bottom|right|width|height)/i.test(f)}});c.DOM=new c.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(f){var h=0,c=1,e=2,d=tinymce.extend;function g(m,k){var j,l;if(m.parentNode!=k){return -1}for(l=k.firstChild,j=0;l!=m;l=l.nextSibling){j++}return j}function b(k){var j=0;while(k.previousSibling){j++;k=k.previousSibling}return j}function i(j,k){var l;if(j.nodeType==3){return j}if(k<0){return j}l=j.firstChild;while(l!=null&&k>0){--k;l=l.nextSibling}if(l!=null){return l}return j}function a(k){var j=k.doc;d(this,{dom:k,startContainer:j,startOffset:0,endContainer:j,endOffset:0,collapsed:true,commonAncestorContainer:j,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3})}d(a.prototype,{setStart:function(k,j){this._setEndPoint(true,k,j)},setEnd:function(k,j){this._setEndPoint(false,k,j)},setStartBefore:function(j){this.setStart(j.parentNode,b(j))},setStartAfter:function(j){this.setStart(j.parentNode,b(j)+1)},setEndBefore:function(j){this.setEnd(j.parentNode,b(j))},setEndAfter:function(j){this.setEnd(j.parentNode,b(j)+1)},collapse:function(k){var j=this;if(k){j.endContainer=j.startContainer;j.endOffset=j.startOffset}else{j.startContainer=j.endContainer;j.startOffset=j.endOffset}j.collapsed=true},selectNode:function(j){this.setStartBefore(j);this.setEndAfter(j)},selectNodeContents:function(j){this.setStart(j,0);this.setEnd(j,j.nodeType===1?j.childNodes.length:j.nodeValue.length)},compareBoundaryPoints:function(m,n){var l=this,p=l.startContainer,o=l.startOffset,k=l.endContainer,j=l.endOffset;if(m===0){return l._compareBoundaryPoints(p,o,p,o)}if(m===1){return l._compareBoundaryPoints(p,o,k,j)}if(m===2){return l._compareBoundaryPoints(k,j,k,j)}if(m===3){return l._compareBoundaryPoints(k,j,p,o)}},deleteContents:function(){this._traverse(e)},extractContents:function(){return this._traverse(h)},cloneContents:function(){return this._traverse(c)},insertNode:function(m){var j=this,l,k;if(m.nodeType===3||m.nodeType===4){l=j.startContainer.splitText(j.startOffset);j.startContainer.parentNode.insertBefore(m,l)}else{if(j.startContainer.childNodes.length>0){k=j.startContainer.childNodes[j.startOffset]}j.startContainer.insertBefore(m,k)}},surroundContents:function(l){var j=this,k=j.extractContents();j.insertNode(l);l.appendChild(k);j.selectNode(l)},cloneRange:function(){var j=this;return d(new a(j.dom),{startContainer:j.startContainer,startOffset:j.startOffset,endContainer:j.endContainer,endOffset:j.endOffset,collapsed:j.collapsed,commonAncestorContainer:j.commonAncestorContainer})},_isCollapsed:function(){return(this.startContainer==this.endContainer&&this.startOffset==this.endOffset)},_compareBoundaryPoints:function(m,p,k,o){var q,l,j,r,t,s;if(m==k){if(p==o){return 0}else{if(p0){l.collapse(k)}}l.collapsed=l._isCollapsed();l.commonAncestorContainer=l.dom.findCommonAncestor(l.startContainer,l.endContainer)},_traverse:function(r){var s=this,q,m=0,v=0,k,o,l,n,j,u;if(s.startContainer==s.endContainer){return s._traverseSameContainer(r)}for(q=s.endContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.startContainer){return s._traverseCommonStartContainer(q,r)}++m}for(q=s.startContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.endContainer){return s._traverseCommonEndContainer(q,r)}++v}o=v-m;l=s.startContainer;while(o>0){l=l.parentNode;o--}n=s.endContainer;while(o<0){n=n.parentNode;o++}for(j=l.parentNode,u=n.parentNode;j!=u;j=j.parentNode,u=u.parentNode){l=j;n=u}return s._traverseCommonAncestors(l,n,r)},_traverseSameContainer:function(o){var r=this,q,u,j,k,l,p,m;if(o!=e){q=r.dom.doc.createDocumentFragment()}if(r.startOffset==r.endOffset){return q}if(r.startContainer.nodeType==3){u=r.startContainer.nodeValue;j=u.substring(r.startOffset,r.endOffset);if(o!=c){r.startContainer.deleteData(r.startOffset,r.endOffset-r.startOffset);r.collapse(true)}if(o==e){return null}q.appendChild(r.dom.doc.createTextNode(j));return q}k=i(r.startContainer,r.startOffset);l=r.endOffset-r.startOffset;while(l>0){p=k.nextSibling;m=r._traverseFullySelected(k,o);if(q){q.appendChild(m)}--l;k=p}if(o!=c){r.collapse(true)}return q},_traverseCommonStartContainer:function(j,p){var s=this,r,k,l,m,q,o;if(p!=e){r=s.dom.doc.createDocumentFragment()}k=s._traverseRightBoundary(j,p);if(r){r.appendChild(k)}l=g(j,s.startContainer);m=l-s.startOffset;if(m<=0){if(p!=c){s.setEndBefore(j);s.collapse(false)}return r}k=j.previousSibling;while(m>0){q=k.previousSibling;o=s._traverseFullySelected(k,p);if(r){r.insertBefore(o,r.firstChild)}--m;k=q}if(p!=c){s.setEndBefore(j);s.collapse(false)}return r},_traverseCommonEndContainer:function(m,p){var s=this,r,o,j,k,q,l;if(p!=e){r=s.dom.doc.createDocumentFragment()}j=s._traverseLeftBoundary(m,p);if(r){r.appendChild(j)}o=g(m,s.endContainer);++o;k=s.endOffset-o;j=m.nextSibling;while(k>0){q=j.nextSibling;l=s._traverseFullySelected(j,p);if(r){r.appendChild(l)}--k;j=q}if(p!=c){s.setStartAfter(m);s.collapse(true)}return r},_traverseCommonAncestors:function(p,j,s){var w=this,l,v,o,q,r,k,u,m;if(s!=e){v=w.dom.doc.createDocumentFragment()}l=w._traverseLeftBoundary(p,s);if(v){v.appendChild(l)}o=p.parentNode;q=g(p,o);r=g(j,o);++q;k=r-q;u=p.nextSibling;while(k>0){m=u.nextSibling;l=w._traverseFullySelected(u,s);if(v){v.appendChild(l)}u=m;--k}l=w._traverseRightBoundary(j,s);if(v){v.appendChild(l)}if(s!=c){w.setStartAfter(p);w.collapse(true)}return v},_traverseRightBoundary:function(p,q){var s=this,l=i(s.endContainer,s.endOffset-1),r,o,n,j,k;var m=l!=s.endContainer;if(l==p){return s._traverseNode(l,m,false,q)}r=l.parentNode;o=s._traverseNode(r,false,false,q);while(r!=null){while(l!=null){n=l.previousSibling;j=s._traverseNode(l,m,false,q);if(q!=e){o.insertBefore(j,o.firstChild)}m=true;l=n}if(r==p){return o}l=r.previousSibling;r=r.parentNode;k=s._traverseNode(r,false,false,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseLeftBoundary:function(p,q){var s=this,m=i(s.startContainer,s.startOffset);var n=m!=s.startContainer,r,o,l,j,k;if(m==p){return s._traverseNode(m,n,true,q)}r=m.parentNode;o=s._traverseNode(r,false,true,q);while(r!=null){while(m!=null){l=m.nextSibling;j=s._traverseNode(m,n,true,q);if(q!=e){o.appendChild(j)}n=true;m=l}if(r==p){return o}m=r.nextSibling;r=r.parentNode;k=s._traverseNode(r,false,true,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseNode:function(j,o,r,s){var u=this,m,l,p,k,q;if(o){return u._traverseFullySelected(j,s)}if(j.nodeType==3){m=j.nodeValue;if(r){k=u.startOffset;l=m.substring(k);p=m.substring(0,k)}else{k=u.endOffset;l=m.substring(0,k);p=m.substring(k)}if(s!=c){j.nodeValue=p}if(s==e){return null}q=j.cloneNode(false);q.nodeValue=l;return q}if(s==e){return null}return j.cloneNode(false)},_traverseFullySelected:function(l,k){var j=this;if(k!=e){return k==c?l.cloneNode(true):l}l.parentNode.removeChild(l);return null}});f.Range=a})(tinymce.dom);(function(){function a(e){var d=this,h="\uFEFF",b,g;function c(j,i){if(j&&i){if(j.item&&i.item&&j.item(0)===i.item(0)){return 1}if(j.isEqual&&i.isEqual&&i.isEqual(j)){return 1}}return 0}function f(){var m=e.dom,j=e.getRng(),s=m.createRng(),p,k,n,q,o,l;function i(v){var t=v.parentNode.childNodes,u;for(u=t.length-1;u>=0;u--){if(t[u]==v){return u}}return -1}function r(v){var t=j.duplicate(),B,y,u,w,x=0,z=0,A,C;t.collapse(v);B=t.parentElement();t.pasteHTML(h);u=B.childNodes;for(y=0;y0&&(w.nodeType!==3||u[y-1].nodeType!==3)){z++}if(w.nodeType===3){A=w.nodeValue.indexOf(h);if(A!==-1){x+=A;break}x+=w.nodeValue.length}else{x=0}}t.moveStart("character",-1);t.text="";return{index:z,offset:x,parent:B}}n=j.item?j.item(0):j.parentElement();if(n.ownerDocument!=m.doc){return s}if(j.item||!n.hasChildNodes()){s.setStart(n.parentNode,i(n));s.setEnd(s.startContainer,s.startOffset+1);return s}l=e.isCollapsed();p=r(true);k=r(false);p.parent.normalize();k.parent.normalize();q=p.parent.childNodes[Math.min(p.index,p.parent.childNodes.length-1)];if(q.nodeType!=3){s.setStart(p.parent,p.index)}else{s.setStart(p.parent.childNodes[p.index],p.offset)}o=k.parent.childNodes[Math.min(k.index,k.parent.childNodes.length-1)];if(o.nodeType!=3){if(!l){k.index++}s.setEnd(k.parent,k.index)}else{s.setEnd(k.parent.childNodes[k.index],k.offset)}if(!l){q=s.startContainer;if(q.nodeType==1){s.setStart(q,Math.min(s.startOffset,q.childNodes.length))}o=s.endContainer;if(o.nodeType==1){s.setEnd(o,Math.min(s.endOffset,o.childNodes.length))}}d.addRange(s);return s}this.addRange=function(j){var o,m=e.dom.doc.body,p,k,q,l,n,i;q=j.startContainer;l=j.startOffset;n=j.endContainer;i=j.endOffset;o=m.createTextRange();q=q.nodeType==1?q.childNodes[Math.min(l,q.childNodes.length-1)]:q;n=n.nodeType==1?n.childNodes[Math.min(l==i?i:i-1,n.childNodes.length-1)]:n;if(q==n&&q.nodeType==1){if(/^(IMG|TABLE)$/.test(q.nodeName)&&l!=i){o=m.createControlRange();o.addElement(q)}else{o=m.createTextRange();if(!q.hasChildNodes()&&q.canHaveHTML){q.innerHTML=h}o.moveToElementText(q);if(q.innerHTML==h){o.collapse(true);q.removeChild(q.firstChild)}}if(l==i){o.collapse(i<=j.endContainer.childNodes.length-1)}o.select();return}function r(t,v){var u,s,w;if(t.nodeType!=3){return -1}u=t.nodeValue;s=m.createTextRange();t.nodeValue=u.substring(0,v)+h+u.substring(v);s.moveToElementText(t.parentNode);s.findText(h);w=Math.abs(s.moveStart("character",-1048575));t.nodeValue=u;return w}if(j.collapsed){pos=r(q,l);o=m.createTextRange();o.move("character",pos);o.select();return}else{if(q==n&&q.nodeType==3){p=r(q,l);o.move("character",p);o.moveEnd("character",i-l);o.select();return}p=r(q,l);k=r(n,i);o=m.createTextRange();if(p==-1){o.moveToElementText(q);p=0}else{o.move("character",p)}tmpRng=m.createTextRange();if(k==-1){tmpRng.moveToElementText(n)}else{tmpRng.move("character",k)}o.setEndPoint("EndToEnd",tmpRng);o.select();return}};this.getRangeAt=function(){if(!b||!c(g,e.getRng())){b=f();g=e.getRng()}return b};this.destroy=function(){g=b=null}}tinymce.dom.TridentSelection=a})();(function(){var p=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,i=0,d=Object.prototype.toString,n=false;var b=function(D,t,A,v){A=A||[];var e=t=t||document;if(t.nodeType!==1&&t.nodeType!==9){return[]}if(!D||typeof D!=="string"){return A}var B=[],C,y,G,F,z,s,r=true,w=o(t);p.lastIndex=0;while((C=p.exec(D))!==null){B.push(C[1]);if(C[2]){s=RegExp.rightContext;break}}if(B.length>1&&j.exec(D)){if(B.length===2&&f.relative[B[0]]){y=g(B[0]+B[1],t)}else{y=f.relative[B[0]]?[t]:b(B.shift(),t);while(B.length){D=B.shift();if(f.relative[D]){D+=B.shift()}y=g(D,y)}}}else{if(!v&&B.length>1&&t.nodeType===9&&!w&&f.match.ID.test(B[0])&&!f.match.ID.test(B[B.length-1])){var H=b.find(B.shift(),t,w);t=H.expr?b.filter(H.expr,H.set)[0]:H.set[0]}if(t){var H=v?{expr:B.pop(),set:a(v)}:b.find(B.pop(),B.length===1&&(B[0]==="~"||B[0]==="+")&&t.parentNode?t.parentNode:t,w);y=H.expr?b.filter(H.expr,H.set):H.set;if(B.length>0){G=a(y)}else{r=false}while(B.length){var u=B.pop(),x=u;if(!f.relative[u]){u=""}else{x=B.pop()}if(x==null){x=t}f.relative[u](G,x,w)}}else{G=B=[]}}if(!G){G=y}if(!G){throw"Syntax error, unrecognized expression: "+(u||D)}if(d.call(G)==="[object Array]"){if(!r){A.push.apply(A,G)}else{if(t&&t.nodeType===1){for(var E=0;G[E]!=null;E++){if(G[E]&&(G[E]===true||G[E].nodeType===1&&h(t,G[E]))){A.push(y[E])}}}else{for(var E=0;G[E]!=null;E++){if(G[E]&&G[E].nodeType===1){A.push(y[E])}}}}}else{a(G,A)}if(s){b(s,e,A,v);b.uniqueSort(A)}return A};b.uniqueSort=function(r){if(c){n=false;r.sort(c);if(n){for(var e=1;e":function(w,r,x){var u=typeof r==="string";if(u&&!/\W/.test(r)){r=x?r:r.toUpperCase();for(var s=0,e=w.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,w){var t=u[1].replace(/\\/g,"");if(!w&&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(w,s,t,x){var r=s[1],u=f.filters[r];if(u){return u(w,t,s,x)}else{if(r==="contains"){return(w.textContent||w.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),w=e+"",u=t[2],r=t[4];return e==null?u==="!=":u==="="?w===r:u==="*="?w.indexOf(r)>=0:u==="~="?(" "+w+" ").indexOf(r)>=0:!r?w&&e!==false:u==="!="?w!=r:u==="^="?w.indexOf(r)===0:u==="$="?w.substr(w.length-r.length)===r:u==="|="?w===r||w.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,w){if(typeof v.getElementById!=="undefined"&&!w){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(w,v,t,u){v=v||document;if(!u&&v.nodeType===9&&!o(v)){try{return a(v.querySelectorAll(w),t)}catch(x){}}return e(w,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,w,v,A,x,z){var y=r=="previousSibling"&&!z;for(var t=0,s=A.length;t0){u=e;break}}}e=e[r]}A[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,x){var t=[],u="",v,s=x.nodeType?[x]:x;while((v=f.match.PSEUDO.exec(e))){u+=v[0];e=e.replace(f.match.PSEUDO,"")}e=f.relative[e]?e+"*":e;for(var w=0,r=s.length;w=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){var b=a.each;a.create("tinymce.dom.Element",{Element:function(g,e){var c=this,f,d;e=e||{};c.id=g;c.dom=f=e.dom||a.DOM;c.settings=e;if(!a.isIE){d=c.dom.get(c.id)}b(["getPos","getRect","getParent","add","setStyle","getStyle","setStyles","setAttrib","setAttribs","getAttrib","addClass","removeClass","hasClass","getOuterHTML","setOuterHTML","remove","show","hide","isHidden","setHTML","get"],function(h){c[h]=function(){var j=[g],k;for(k=0;k_';j.deleteContents();j.insertNode(f.getRng().createContextualFragment(i));l=f.dom.get("__caret");j=k.createRange();j.setStartBefore(l);j.setEndAfter(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(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(1);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.firstChild}return h}else{h=g.startContainer;if(h.nodeName=="BODY"){return h.firstChild}return f.dom.getParent(h,"*")}},getEnd:function(){var f=this,g=f.getRng(),h;if(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(0);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.lastChild}return h}else{h=g.endContainer;if(h.nodeName=="BODY"){return h.lastChild}return f.dom.getParent(h,"*")}},getBookmark:function(x){var j=this,m=j.getRng(),f,n,l,u=j.dom.getViewPort(j.win),v,p,z,o,w=-16777215,k,h=j.dom.getRoot(),g=0,i=0,y;n=u.x;l=u.y;if(x){return{rng:m,scrollX:n,scrollY:l}}if(a){if(m.item){v=m.item(0);d(j.dom.select(v.nodeName),function(s,r){if(v==s){p=r;return false}});return{tag:v.nodeName,index:p,scrollX:n,scrollY:l}}f=j.dom.doc.body.createTextRange();f.moveToElementText(h);f.collapse(true);z=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(true);p=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(false);o=Math.abs(f.move("character",w))-p;return{start:p-z,length:o,scrollX:n,scrollY:l}}v=j.getNode();k=j.getSel();if(!k){return null}if(v&&v.nodeName=="IMG"){return{scrollX:n,scrollY:l}}function q(A,D,t){var s=j.dom.doc.createTreeWalker(A,NodeFilter.SHOW_TEXT,null,false),E,B=0,C={};while((E=s.nextNode())!=null){if(E==D){C.start=B}if(E==t){C.end=B;return C}B+=e(E.nodeValue||"").length}return null}if(k.anchorNode==k.focusNode&&k.anchorOffset==k.focusOffset){v=q(h,k.anchorNode,k.focusNode);if(!v){return{scrollX:n,scrollY:l}}e(k.anchorNode.nodeValue||"").replace(/^\s+/,function(r){g=r.length});return{start:Math.max(v.start+k.anchorOffset-g,0),end:Math.max(v.end+k.focusOffset-g,0),scrollX:n,scrollY:l,beg:k.anchorOffset-g==0}}else{v=q(h,m.startContainer,m.endContainer);if(!v){return{scrollX:n,scrollY:l}}return{start:Math.max(v.start+m.startOffset-g,0),end:Math.max(v.end+m.endOffset-i,0),scrollX:n,scrollY:l,beg:m.startOffset-g==0}}},moveToBookmark:function(n){var o=this,g=o.getRng(),p=o.getSel(),j=o.dom.getRoot(),m,h,k;function i(q,t,D){var B=o.dom.doc.createTreeWalker(q,NodeFilter.SHOW_TEXT,null,false),x,s=0,A={},u,C,z,y;while((x=B.nextNode())!=null){z=y=0;k=x.nodeValue||"";h=e(k).length;s+=h;if(s>=t&&!A.startNode){u=t-(s-h);if(n.beg&&u>=h){continue}A.startNode=x;A.startOffset=u+y}if(s>=D){A.endNode=x;A.endOffset=D-(s-h)+y;return A}}return null}if(!n){return false}o.win.scrollTo(n.scrollX,n.scrollY);if(a){if(g=n.rng){try{g.select()}catch(l){}return true}o.win.focus();if(n.tag){g=j.createControlRange();d(o.dom.select(n.tag),function(r,q){if(q==n.index){g.addElement(r)}})}else{try{if(n.start<0){return true}g=p.createRange();g.moveToElementText(j);g.collapse(true);g.moveStart("character",n.start);g.moveEnd("character",n.length)}catch(f){return true}}try{g.select()}catch(l){}return true}if(!p){return false}if(n.rng){p.removeAllRanges();p.addRange(n.rng)}else{if(b(n.start)&&b(n.end)){try{m=i(j,n.start,n.end);if(m){g=o.dom.doc.createRange();g.setStart(m.startNode,m.startOffset);g.setEnd(m.endNode,m.endOffset);p.removeAllRanges();p.addRange(g)}if(!c.isOpera){o.win.focus()}}catch(l){}}}},select:function(g,l){var p=this,f=p.getRng(),q=p.getSel(),o,m,k,j=p.win.document;function h(u,t){var s,r;if(u){s=j.createTreeWalker(u,NodeFilter.SHOW_TEXT,null,false);while(u=s.nextNode()){r=u;if(c.trim(u.nodeValue).length!=0){if(t){return u}else{r=u}}}}return r}if(a){try{o=j.body;if(/^(IMG|TABLE)$/.test(g.nodeName)){f=o.createControlRange();f.addElement(g)}else{f=o.createTextRange();f.moveToElementText(g)}f.select()}catch(i){}}else{if(l){m=h(g,1)||p.dom.select("br:first",g)[0];k=h(g,0)||p.dom.select("br:last",g)[0];if(m&&k){f=j.createRange();if(m.nodeName=="BR"){f.setStartBefore(m)}else{f.setStart(m,0)}if(k.nodeName=="BR"){f.setEndBefore(k)}else{f.setEnd(k,k.nodeValue.length)}}else{f.selectNode(g)}}else{f.selectNode(g)}p.setRng(f)}return g},isCollapsed:function(){var f=this,h=f.getRng(),g=f.getSel();if(!h||h.item){return false}return !g||h.boundingWidth==0||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=a?g.win.document.body.createTextRange():g.win.document.createRange()}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 f=this,h=f.getRng(),g=f.getSel(),i;if(!a){if(!h){return f.dom.getRoot()}i=h.commonAncestorContainer;if(!h.collapsed){if(c.isWebKit&&g.anchorNode&&g.anchorNode.nodeType==1){return g.anchorNode.childNodes[g.anchorOffset]}if(h.startContainer==h.endContainer){if(h.startOffset-h.endOffset<2){if(h.startContainer.hasChildNodes()){i=h.startContainer.childNodes[h.startOffset]}}}}return f.dom.getParent(i,"*")}return h.item?h.item(0):h.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(""+b+">")}if(this.settings.indentation>0){this.writeRaw("\n")}}},writeFullEndElement:function(){if(this.tags.length>0){this._writeAttributesEnd();this.writeRaw(""+this.tags.pop()+">");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",bool_attrs:/(checked|disabled|readonly|selected|nowrap)/,valid_elements:"*[*]",extended_valid_elements:0,valid_child_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,font_size_style_values: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;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""+o+">"}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,y,w=["ol","ul"],u,t,q,k=/^(OL|UL)$/,z;function m(r,x){var o=x.split(","),p;while((r=r.previousSibling)!=null){for(p=0;p1){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]*>)(.*?)(<\/script>)/g},{pattern:/(]*>)(.*?)(<\/noscript>)/g},{pattern:/("});j=j.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(h,l,k){return""})}j=j.replace(//g,"");j=j.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi,function(h,l){function k(o,n,q){var p=q;if(h.indexOf("mce_"+n)!=-1){return o}if(n=="style"){if(g._isRes(q)){return o}if(i.hex_colors){p=p.replace(/rgb\([^\)]+\)/g,function(m){return g.toHex(m)})}if(i.url_converter){p=p.replace(/url\([\'\"]?([^\)\'\"]+)\)/g,function(m,r){return"url("+g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(r),n,l))+")"})}}else{if(n!="coords"&&n!="shape"){if(i.url_converter){p=g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(q),n,l))}}}return" "+n+'="'+q+'" mce_'+n+'="'+p+'"'}h=h.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi,k);h=h.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi,k);return h.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi,k)})}return j},getOuterHTML:function(f){var g;f=this.get(f);if(!f){return null}if(f.outerHTML!==undefined){return f.outerHTML}g=(f.ownerDocument||this.doc).createElement("body");g.appendChild(f.cloneNode(true));return g.innerHTML},setOuterHTML:function(i,g,j){var f=this;return this.run(i,function(h){var l,k;h=f.get(h);j=j||h.ownerDocument||f.doc;if(a&&h.nodeType==1){h.outerHTML=g}else{k=j.createElement("body");k.innerHTML=g;l=k.lastChild;while(l){f.insertAfter(l.cloneNode(true),h);l=l.previousSibling}f.remove(h)}})},decode:function(g){var h,i,f;if(/&[^;]+;/.test(g)){h=this.doc.createElement("div");h.innerHTML=g;i=h.firstChild;f="";if(i){do{f+=i.nodeValue}while(i.nextSibling)}return f||g}return g},encode:function(f){return f?(""+f).replace(/[<>&\"]/g,function(h,g){switch(h){case"&":return"&";case'"':return""";case"<":return"<";case">":return">"}return h}):f},insertAfter:function(h,g){var f=this;g=f.get(g);return this.run(h,function(k){var j,i;j=g.parentNode;i=g.nextSibling;if(i){j.insertBefore(k,i)}else{j.appendChild(k)}return k})},isBlock:function(f){if(f.nodeType&&f.nodeType!==1){return false}f=f.nodeName||f;return/^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(f)},replace:function(i,h,f){var g=this;if(b(h,"array")){i=i.cloneNode(true)}return g.run(h,function(j){if(f){e(j.childNodes,function(k){i.appendChild(k.cloneNode(true))})}if(g.fixPsuedoLeaks&&j.nodeType===1){j.parentNode.insertBefore(i,j);g.remove(j);return i}return j.parentNode.replaceChild(i,j)})},findCommonAncestor:function(h,f){var i=h,g;while(i){g=f;while(g&&i!=g){g=g.parentNode}if(i==g){break}i=i.parentNode}if(!i&&h.ownerDocument){return h.ownerDocument.documentElement}return i},toHex:function(f){var h=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(f);function g(i){i=parseInt(i).toString(16);return i.length>1?i:"0"+i}if(h){f="#"+g(h[1])+g(h[2])+g(h[3]);return f}return f},getClasses:function(){var l=this,g=[],k,m={},n=l.settings.class_filter,j;if(l.classes){return l.classes}function o(f){e(f.imports,function(i){o(i)});e(f.cssRules||f.rules,function(i){switch(i.type||1){case 1:if(i.selectorText){e(i.selectorText.split(","),function(p){p=p.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(p)||!/\.[\w\-]+$/.test(p)){return}j=p;p=p.replace(/.*\.([a-z0-9_\-]+).*/i,"$1");if(n&&!(p=n(p,j))){return}if(!m[p]){g.push({"class":p});m[p]=1}})}break;case 3:o(i.styleSheet);break}})}try{e(l.doc.styleSheets,o)}catch(h){}if(g.length>0){l.classes=g}return g},run:function(j,i,h){var g=this,k;if(g.doc&&typeof(j)==="string"){j=g.get(j)}if(!j){return false}h=h||this;if(!j.nodeType&&(j.length||j.length===0)){k=[];e(j,function(l,f){if(l){if(typeof(l)=="string"){l=g.doc.getElementById(l)}k.push(i.call(h,l,f))}});return k}return i.call(h,j)},getAttribs:function(g){var f;g=this.get(g);if(!g){return[]}if(a){f=[];if(g.nodeName=="OBJECT"){return g.attributes}g.cloneNode(false).outerHTML.replace(/([a-z0-9\:\-_]+)=/gi,function(i,h){f.push({specified:1,nodeName:h})});return f}return g.attributes},destroy:function(g){var f=this;if(f.events){f.events.destroy()}f.win=f.doc=f.root=f.events=null;if(!g){c.removeUnload(f.destroy)}},createRng:function(){var f=this.doc;return f.createRange?f.createRange():new c.dom.Range(this)},split:function(k,j,n){var o=this,f=o.createRng(),l,i,m;function g(q,p){q=q[p];if(q&&q[p]&&q[p].nodeType==1&&h(q[p])){o.remove(q[p])}}function h(p){p=o.getOuterHTML(p);p=p.replace(/<(img|hr|table)/gi,"-");p=p.replace(/<[^>]+>/g,"");return p.replace(/[ \t\r\n]+| | /g,"")==""}if(k&&j){f.setStartBefore(k);f.setEndBefore(j);l=f.extractContents();f=o.createRng();f.setStartAfter(j);f.setEndAfter(k);i=f.extractContents();m=k.parentNode;g(l,"lastChild");if(!h(l)){m.insertBefore(l,k)}if(n){m.replaceChild(n,j)}else{m.insertBefore(j,k)}g(i,"firstChild");if(!h(i)){m.insertBefore(i,k)}o.remove(k);return n||j}},bind:function(j,f,i,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.add(j,f,i,h||this)},unbind:function(i,f,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.remove(i,f,h)},_isRes:function(f){return/^(top|left|bottom|right|width|height)/i.test(f)||/;\s*(top|left|bottom|right|width|height)/i.test(f)}});c.DOM=new c.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(f){var h=0,c=1,e=2,d=tinymce.extend;function g(m,k){var j,l;if(m.parentNode!=k){return -1}for(l=k.firstChild,j=0;l!=m;l=l.nextSibling){j++}return j}function b(k){var j=0;while(k.previousSibling){j++;k=k.previousSibling}return j}function i(j,k){var l;if(j.nodeType==3){return j}if(k<0){return j}l=j.firstChild;while(l!=null&&k>0){--k;l=l.nextSibling}if(l!=null){return l}return j}function a(k){var j=k.doc;d(this,{dom:k,startContainer:j,startOffset:0,endContainer:j,endOffset:0,collapsed:true,commonAncestorContainer:j,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3})}d(a.prototype,{setStart:function(k,j){this._setEndPoint(true,k,j)},setEnd:function(k,j){this._setEndPoint(false,k,j)},setStartBefore:function(j){this.setStart(j.parentNode,b(j))},setStartAfter:function(j){this.setStart(j.parentNode,b(j)+1)},setEndBefore:function(j){this.setEnd(j.parentNode,b(j))},setEndAfter:function(j){this.setEnd(j.parentNode,b(j)+1)},collapse:function(k){var j=this;if(k){j.endContainer=j.startContainer;j.endOffset=j.startOffset}else{j.startContainer=j.endContainer;j.startOffset=j.endOffset}j.collapsed=true},selectNode:function(j){this.setStartBefore(j);this.setEndAfter(j)},selectNodeContents:function(j){this.setStart(j,0);this.setEnd(j,j.nodeType===1?j.childNodes.length:j.nodeValue.length)},compareBoundaryPoints:function(m,n){var l=this,p=l.startContainer,o=l.startOffset,k=l.endContainer,j=l.endOffset;if(m===0){return l._compareBoundaryPoints(p,o,p,o)}if(m===1){return l._compareBoundaryPoints(p,o,k,j)}if(m===2){return l._compareBoundaryPoints(k,j,k,j)}if(m===3){return l._compareBoundaryPoints(k,j,p,o)}},deleteContents:function(){this._traverse(e)},extractContents:function(){return this._traverse(h)},cloneContents:function(){return this._traverse(c)},insertNode:function(m){var j=this,l,k;if(m.nodeType===3||m.nodeType===4){l=j.startContainer.splitText(j.startOffset);j.startContainer.parentNode.insertBefore(m,l)}else{if(j.startContainer.childNodes.length>0){k=j.startContainer.childNodes[j.startOffset]}j.startContainer.insertBefore(m,k)}},surroundContents:function(l){var j=this,k=j.extractContents();j.insertNode(l);l.appendChild(k);j.selectNode(l)},cloneRange:function(){var j=this;return d(new a(j.dom),{startContainer:j.startContainer,startOffset:j.startOffset,endContainer:j.endContainer,endOffset:j.endOffset,collapsed:j.collapsed,commonAncestorContainer:j.commonAncestorContainer})},_isCollapsed:function(){return(this.startContainer==this.endContainer&&this.startOffset==this.endOffset)},_compareBoundaryPoints:function(m,p,k,o){var q,l,j,r,t,s;if(m==k){if(p==o){return 0}else{if(p0){l.collapse(k)}}l.collapsed=l._isCollapsed();l.commonAncestorContainer=l.dom.findCommonAncestor(l.startContainer,l.endContainer)},_traverse:function(r){var s=this,q,m=0,v=0,k,o,l,n,j,u;if(s.startContainer==s.endContainer){return s._traverseSameContainer(r)}for(q=s.endContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.startContainer){return s._traverseCommonStartContainer(q,r)}++m}for(q=s.startContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.endContainer){return s._traverseCommonEndContainer(q,r)}++v}o=v-m;l=s.startContainer;while(o>0){l=l.parentNode;o--}n=s.endContainer;while(o<0){n=n.parentNode;o++}for(j=l.parentNode,u=n.parentNode;j!=u;j=j.parentNode,u=u.parentNode){l=j;n=u}return s._traverseCommonAncestors(l,n,r)},_traverseSameContainer:function(o){var r=this,q,u,j,k,l,p,m;if(o!=e){q=r.dom.doc.createDocumentFragment()}if(r.startOffset==r.endOffset){return q}if(r.startContainer.nodeType==3){u=r.startContainer.nodeValue;j=u.substring(r.startOffset,r.endOffset);if(o!=c){r.startContainer.deleteData(r.startOffset,r.endOffset-r.startOffset);r.collapse(true)}if(o==e){return null}q.appendChild(r.dom.doc.createTextNode(j));return q}k=i(r.startContainer,r.startOffset);l=r.endOffset-r.startOffset;while(l>0){p=k.nextSibling;m=r._traverseFullySelected(k,o);if(q){q.appendChild(m)}--l;k=p}if(o!=c){r.collapse(true)}return q},_traverseCommonStartContainer:function(j,p){var s=this,r,k,l,m,q,o;if(p!=e){r=s.dom.doc.createDocumentFragment()}k=s._traverseRightBoundary(j,p);if(r){r.appendChild(k)}l=g(j,s.startContainer);m=l-s.startOffset;if(m<=0){if(p!=c){s.setEndBefore(j);s.collapse(false)}return r}k=j.previousSibling;while(m>0){q=k.previousSibling;o=s._traverseFullySelected(k,p);if(r){r.insertBefore(o,r.firstChild)}--m;k=q}if(p!=c){s.setEndBefore(j);s.collapse(false)}return r},_traverseCommonEndContainer:function(m,p){var s=this,r,o,j,k,q,l;if(p!=e){r=s.dom.doc.createDocumentFragment()}j=s._traverseLeftBoundary(m,p);if(r){r.appendChild(j)}o=g(m,s.endContainer);++o;k=s.endOffset-o;j=m.nextSibling;while(k>0){q=j.nextSibling;l=s._traverseFullySelected(j,p);if(r){r.appendChild(l)}--k;j=q}if(p!=c){s.setStartAfter(m);s.collapse(true)}return r},_traverseCommonAncestors:function(p,j,s){var w=this,l,v,o,q,r,k,u,m;if(s!=e){v=w.dom.doc.createDocumentFragment()}l=w._traverseLeftBoundary(p,s);if(v){v.appendChild(l)}o=p.parentNode;q=g(p,o);r=g(j,o);++q;k=r-q;u=p.nextSibling;while(k>0){m=u.nextSibling;l=w._traverseFullySelected(u,s);if(v){v.appendChild(l)}u=m;--k}l=w._traverseRightBoundary(j,s);if(v){v.appendChild(l)}if(s!=c){w.setStartAfter(p);w.collapse(true)}return v},_traverseRightBoundary:function(p,q){var s=this,l=i(s.endContainer,s.endOffset-1),r,o,n,j,k;var m=l!=s.endContainer;if(l==p){return s._traverseNode(l,m,false,q)}r=l.parentNode;o=s._traverseNode(r,false,false,q);while(r!=null){while(l!=null){n=l.previousSibling;j=s._traverseNode(l,m,false,q);if(q!=e){o.insertBefore(j,o.firstChild)}m=true;l=n}if(r==p){return o}l=r.previousSibling;r=r.parentNode;k=s._traverseNode(r,false,false,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseLeftBoundary:function(p,q){var s=this,m=i(s.startContainer,s.startOffset);var n=m!=s.startContainer,r,o,l,j,k;if(m==p){return s._traverseNode(m,n,true,q)}r=m.parentNode;o=s._traverseNode(r,false,true,q);while(r!=null){while(m!=null){l=m.nextSibling;j=s._traverseNode(m,n,true,q);if(q!=e){o.appendChild(j)}n=true;m=l}if(r==p){return o}m=r.nextSibling;r=r.parentNode;k=s._traverseNode(r,false,true,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseNode:function(j,o,r,s){var u=this,m,l,p,k,q;if(o){return u._traverseFullySelected(j,s)}if(j.nodeType==3){m=j.nodeValue;if(r){k=u.startOffset;l=m.substring(k);p=m.substring(0,k)}else{k=u.endOffset;l=m.substring(0,k);p=m.substring(k)}if(s!=c){j.nodeValue=p}if(s==e){return null}q=j.cloneNode(false);q.nodeValue=l;return q}if(s==e){return null}return j.cloneNode(false)},_traverseFullySelected:function(l,k){var j=this;if(k!=e){return k==c?l.cloneNode(true):l}l.parentNode.removeChild(l);return null}});f.Range=a})(tinymce.dom);(function(){function a(e){var d=this,h="\uFEFF",b,g;function c(j,i){if(j&&i){if(j.item&&i.item&&j.item(0)===i.item(0)){return 1}if(j.isEqual&&i.isEqual&&i.isEqual(j)){return 1}}return 0}function f(){var m=e.dom,j=e.getRng(),s=m.createRng(),p,k,n,q,o,l;function i(v){var t=v.parentNode.childNodes,u;for(u=t.length-1;u>=0;u--){if(t[u]==v){return u}}return -1}function r(v){var t=j.duplicate(),B,y,u,w,x=0,z=0,A,C;t.collapse(v);B=t.parentElement();t.pasteHTML(h);u=B.childNodes;for(y=0;y0&&(w.nodeType!==3||u[y-1].nodeType!==3)){z++}if(w.nodeType===3){A=w.nodeValue.indexOf(h);if(A!==-1){x+=A;break}x+=w.nodeValue.length}else{x=0}}t.moveStart("character",-1);t.text="";return{index:z,offset:x,parent:B}}n=j.item?j.item(0):j.parentElement();if(n.ownerDocument!=m.doc){return s}if(j.item||!n.hasChildNodes()){s.setStart(n.parentNode,i(n));s.setEnd(s.startContainer,s.startOffset+1);return s}l=e.isCollapsed();p=r(true);k=r(false);p.parent.normalize();k.parent.normalize();q=p.parent.childNodes[Math.min(p.index,p.parent.childNodes.length-1)];if(q.nodeType!=3){s.setStart(p.parent,p.index)}else{s.setStart(p.parent.childNodes[p.index],p.offset)}o=k.parent.childNodes[Math.min(k.index,k.parent.childNodes.length-1)];if(o.nodeType!=3){if(!l){k.index++}s.setEnd(k.parent,k.index)}else{s.setEnd(k.parent.childNodes[k.index],k.offset)}if(!l){q=s.startContainer;if(q.nodeType==1){s.setStart(q,Math.min(s.startOffset,q.childNodes.length))}o=s.endContainer;if(o.nodeType==1){s.setEnd(o,Math.min(s.endOffset,o.childNodes.length))}}d.addRange(s);return s}this.addRange=function(j){var o,m=e.dom.doc.body,p,k,q,l,n,i;q=j.startContainer;l=j.startOffset;n=j.endContainer;i=j.endOffset;o=m.createTextRange();q=q.nodeType==1?q.childNodes[Math.min(l,q.childNodes.length-1)]:q;n=n.nodeType==1?n.childNodes[Math.min(l==i?i:i-1,n.childNodes.length-1)]:n;if(q==n&&q.nodeType==1){if(/^(IMG|TABLE)$/.test(q.nodeName)&&l!=i){o=m.createControlRange();o.addElement(q)}else{o=m.createTextRange();if(!q.hasChildNodes()&&q.canHaveHTML){q.innerHTML=h}o.moveToElementText(q);if(q.innerHTML==h){o.collapse(true);q.removeChild(q.firstChild)}}if(l==i){o.collapse(i<=j.endContainer.childNodes.length-1)}o.select();return}function r(t,v){var u,s,w;if(t.nodeType!=3){return -1}u=t.nodeValue;s=m.createTextRange();t.nodeValue=u.substring(0,v)+h+u.substring(v);s.moveToElementText(t.parentNode);s.findText(h);w=Math.abs(s.moveStart("character",-1048575));t.nodeValue=u;return w}if(j.collapsed){pos=r(q,l);o=m.createTextRange();o.move("character",pos);o.select();return}else{if(q==n&&q.nodeType==3){p=r(q,l);o.move("character",p);o.moveEnd("character",i-l);o.select();return}p=r(q,l);k=r(n,i);o=m.createTextRange();if(p==-1){o.moveToElementText(q);p=0}else{o.move("character",p)}tmpRng=m.createTextRange();if(k==-1){tmpRng.moveToElementText(n)}else{tmpRng.move("character",k)}o.setEndPoint("EndToEnd",tmpRng);o.select();return}};this.getRangeAt=function(){if(!b||!c(g,e.getRng())){b=f();g=e.getRng()}return b};this.destroy=function(){g=b=null}}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){var b=a.each;a.create("tinymce.dom.Element",{Element:function(g,e){var c=this,f,d;e=e||{};c.id=g;c.dom=f=e.dom||a.DOM;c.settings=e;if(!a.isIE){d=c.dom.get(c.id)}b(["getPos","getRect","getParent","add","setStyle","getStyle","setStyles","setAttrib","setAttribs","getAttrib","addClass","removeClass","hasClass","getOuterHTML","setOuterHTML","remove","show","hide","isHidden","setHTML","get"],function(h){c[h]=function(){var j=[g],k;for(k=0;k_';j.deleteContents();j.insertNode(f.getRng().createContextualFragment(i));l=f.dom.get("__caret");j=k.createRange();j.setStartBefore(l);j.setEndAfter(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(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(1);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.firstChild}return h}else{h=g.startContainer;if(h.nodeName=="BODY"){return h.firstChild}return f.dom.getParent(h,"*")}},getEnd:function(){var f=this,g=f.getRng(),h;if(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(0);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.lastChild}return h}else{h=g.endContainer;if(h.nodeName=="BODY"){return h.lastChild}return f.dom.getParent(h,"*")}},getBookmark:function(x){var j=this,m=j.getRng(),f,n,l,u=j.dom.getViewPort(j.win),v,p,z,o,w=-16777215,k,h=j.dom.getRoot(),g=0,i=0,y;n=u.x;l=u.y;if(x){return{rng:m,scrollX:n,scrollY:l}}if(a){if(m.item){v=m.item(0);d(j.dom.select(v.nodeName),function(s,r){if(v==s){p=r;return false}});return{tag:v.nodeName,index:p,scrollX:n,scrollY:l}}f=j.dom.doc.body.createTextRange();f.moveToElementText(h);f.collapse(true);z=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(true);p=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(false);o=Math.abs(f.move("character",w))-p;return{start:p-z,length:o,scrollX:n,scrollY:l}}v=j.getNode();k=j.getSel();if(!k){return null}if(v&&v.nodeName=="IMG"){return{scrollX:n,scrollY:l}}function q(A,D,t){var s=j.dom.doc.createTreeWalker(A,NodeFilter.SHOW_TEXT,null,false),E,B=0,C={};while((E=s.nextNode())!=null){if(E==D){C.start=B}if(E==t){C.end=B;return C}B+=e(E.nodeValue||"").length}return null}if(k.anchorNode==k.focusNode&&k.anchorOffset==k.focusOffset){v=q(h,k.anchorNode,k.focusNode);if(!v){return{scrollX:n,scrollY:l}}e(k.anchorNode.nodeValue||"").replace(/^\s+/,function(r){g=r.length});return{start:Math.max(v.start+k.anchorOffset-g,0),end:Math.max(v.end+k.focusOffset-g,0),scrollX:n,scrollY:l,beg:k.anchorOffset-g==0}}else{v=q(h,m.startContainer,m.endContainer);if(!v){return{scrollX:n,scrollY:l}}return{start:Math.max(v.start+m.startOffset-g,0),end:Math.max(v.end+m.endOffset-i,0),scrollX:n,scrollY:l,beg:m.startOffset-g==0}}},moveToBookmark:function(n){var o=this,g=o.getRng(),p=o.getSel(),j=o.dom.getRoot(),m,h,k;function i(q,t,D){var B=o.dom.doc.createTreeWalker(q,NodeFilter.SHOW_TEXT,null,false),x,s=0,A={},u,C,z,y;while((x=B.nextNode())!=null){z=y=0;k=x.nodeValue||"";h=e(k).length;s+=h;if(s>=t&&!A.startNode){u=t-(s-h);if(n.beg&&u>=h){continue}A.startNode=x;A.startOffset=u+y}if(s>=D){A.endNode=x;A.endOffset=D-(s-h)+y;return A}}return null}if(!n){return false}o.win.scrollTo(n.scrollX,n.scrollY);if(a){if(g=n.rng){try{g.select()}catch(l){}return true}o.win.focus();if(n.tag){g=j.createControlRange();d(o.dom.select(n.tag),function(r,q){if(q==n.index){g.addElement(r)}})}else{try{if(n.start<0){return true}g=p.createRange();g.moveToElementText(j);g.collapse(true);g.moveStart("character",n.start);g.moveEnd("character",n.length)}catch(f){return true}}try{g.select()}catch(l){}return true}if(!p){return false}if(n.rng){p.removeAllRanges();p.addRange(n.rng)}else{if(b(n.start)&&b(n.end)){try{m=i(j,n.start,n.end);if(m){g=o.dom.doc.createRange();g.setStart(m.startNode,m.startOffset);g.setEnd(m.endNode,m.endOffset);p.removeAllRanges();p.addRange(g)}if(!c.isOpera){o.win.focus()}}catch(l){}}}},select:function(g,l){var p=this,f=p.getRng(),q=p.getSel(),o,m,k,j=p.win.document;function h(u,t){var s,r;if(u){s=j.createTreeWalker(u,NodeFilter.SHOW_TEXT,null,false);while(u=s.nextNode()){r=u;if(c.trim(u.nodeValue).length!=0){if(t){return u}else{r=u}}}}return r}if(a){try{o=j.body;if(/^(IMG|TABLE)$/.test(g.nodeName)){f=o.createControlRange();f.addElement(g)}else{f=o.createTextRange();f.moveToElementText(g)}f.select()}catch(i){}}else{if(l){m=h(g,1)||p.dom.select("br:first",g)[0];k=h(g,0)||p.dom.select("br:last",g)[0];if(m&&k){f=j.createRange();if(m.nodeName=="BR"){f.setStartBefore(m)}else{f.setStart(m,0)}if(k.nodeName=="BR"){f.setEndBefore(k)}else{f.setEnd(k,k.nodeValue.length)}}else{f.selectNode(g)}}else{f.selectNode(g)}p.setRng(f)}return g},isCollapsed:function(){var f=this,h=f.getRng(),g=f.getSel();if(!h||h.item){return false}return !g||h.boundingWidth==0||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=a?g.win.document.body.createTextRange():g.win.document.createRange()}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 f=this,h=f.getRng(),g=f.getSel(),i;if(!a){if(!h){return f.dom.getRoot()}i=h.commonAncestorContainer;if(!h.collapsed){if(c.isWebKit&&g.anchorNode&&g.anchorNode.nodeType==1){return g.anchorNode.childNodes[g.anchorOffset]}if(h.startContainer==h.endContainer){if(h.startOffset-h.endOffset<2){if(h.startContainer.hasChildNodes()){i=h.startContainer.childNodes[h.startOffset]}}}}return f.dom.getParent(i,"*")}return h.item?h.item(0):h.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(""+b+">")}if(this.settings.indentation>0){this.writeRaw("\n")}}},writeFullEndElement:function(){if(this.tags.length>0){this._writeAttributesEnd();this.writeRaw(""+this.tags.pop()+">");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",bool_attrs:/(checked|disabled|readonly|selected|nowrap)/,valid_elements:"*[*]",extended_valid_elements:0,valid_child_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,font_size_style_values: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;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""+o+">"}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,y,w=["ol","ul"],u,t,q,k=/^(OL|UL)$/,z;function m(r,x){var o=x.split(","),p;while((r=r.previousSibling)!=null){for(p=0;p1){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]*>)(.*?)(<\/script>)/g},{pattern:/(]*>)(.*?)(<\/noscript>)/g},{pattern:/('; + }); + + // Wrap noscript elements + h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { + return ''; + }); + } + + h = h.replace(//g, ''); + + // Process all tags with src, href or style + h = h.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi, function(a, n) { + function handle(m, b, c) { + var u = c; + + // Tag already got a mce_ version + if (a.indexOf('mce_' + b) != -1) + return m; + + if (b == 'style') { + // No mce_style for elements with these since they might get resized by the user + if (t._isRes(c)) + return m; + + if (s.hex_colors) { + u = u.replace(/rgb\([^\)]+\)/g, function(v) { + return t.toHex(v); + }); + } + + if (s.url_converter) { + u = u.replace(/url\([\'\"]?([^\)\'\"]+)\)/g, function(x, c) { + return 'url(' + t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)) + ')'; + }); + } + } else if (b != 'coords' && b != 'shape') { + if (s.url_converter) + u = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)); + } + + return ' ' + b + '="' + c + '" mce_' + b + '="' + u + '"'; + }; + + a = a.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi, handle); // W3C + a = a.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi, handle); // W3C + + return a.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi, handle); // IE + }); + } + + 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; + + return this.run(e, function(e) { + var n, tp; + + e = t.get(e); + d = d || e.ownerDocument || t.doc; + + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else { + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + } + }); + }, + + decode : function(s) { + var e, n, v; + + // Look for entities to decode + if (/&[^;]+;/.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.nextSibling); + } + + return v || s; + } + + return s; + }, + + encode : function(s) { + return s ? ('' + s).replace(/[<>&\"]/g, function (c, b) { + switch (c) { + case '&': + return '&'; + + case '"': + return '"'; + + case '<': + return '<'; + + case '>': + return '>'; + } + + return c; + }) : s; + }, + + insertAfter : function(n, r) { + var t = this; + + r = t.get(r); + + return this.run(n, function(n) { + var p, ns; + + p = r.parentNode; + ns = r.nextSibling; + + if (ns) + p.insertBefore(n, ns); + else + p.appendChild(n); + + return n; + }); + }, + + isBlock : function(n) { + if (n.nodeType && n.nodeType !== 1) + return false; + + n = n.nodeName || n; + + return /^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.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(o.childNodes, function(c) { + n.appendChild(c.cloneNode(true)); + }); + } + + // Fix IE psuedo leak for elements since replacing elements if fairly common + // Will break parentNode for some unknown reason + if (t.fixPsuedoLeaks && o.nodeType === 1) { + o.parentNode.insertBefore(n, o); + t.remove(o); + return n; + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + 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; + + // 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(/([a-z0-9\:\-_]+)=/gi, function(a, b) { + o.push({specified : 1, nodeName : b}); + }); + + 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); + }, + + 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 sence + // but we don't want that in our code since it serves no purpose + // For example if this is chopped: + // text 1CHOPtext 2 + // would produce: + // text 1CHOPtext 2 + // this function will then trim of empty edges and produce: + // text 1CHOPtext 2 + function trimEdge(n, na) { + n = n[na]; + + if (n && n[na] && n[na].nodeType == 1 && isEmpty(n[na])) + t.remove(n[na]); + }; + + function isEmpty(n) { + n = t.getOuterHTML(n); + n = n.replace(/<(img|hr|table)/gi, '-'); // Keep these convert them to - chars + n = n.replace(/<[^>]+>/g, ''); // Remove all tags + + return n.replace(/[ \t\r\n]+| | /g, '') == ''; + }; + + if (pe && e) { + // Get before chunk + r.setStartBefore(pe); + r.setEndBefore(e); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStartAfter(e); + r.setEndAfter(pe); + aft = r.extractContents(); + + // Insert chunks and remove parent + pa = pe.parentNode; + + // Remove right side edge of the before contents + trimEdge(bef, 'lastChild'); + + if (!isEmpty(bef)) + pa.insertBefore(bef, pe); + + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Remove left site edge of the after contents + trimEdge(aft, 'firstChild'); + + if (!isEmpty(aft)) + pa.insertBefore(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); + }, + + + _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; + } + */ + + }); + + // Setup page DOM + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); +(function(ns) { + // Traverse constants + var EXTRACT = 0, CLONE = 1, DELETE = 2, extend = tinymce.extend; + + function indexOf(child, parent) { + var i, node; + + if (child.parentNode != parent) + return -1; + + for (node = parent.firstChild, i = 0; node != child; node = node.nextSibling) + i++; + + return i; + }; + + function nodeIndex(n) { + var i = 0; + + while (n.previousSibling) { + i++; + n = n.previousSibling; + } + + return i; + }; + + function getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child != null && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child != null) + return child; + + return container; + }; + + // Range constructor + function Range(dom) { + var d = dom.doc; + + extend(this, { + dom : dom, + + // Inital states + startContainer : d, + startOffset : 0, + endContainer : d, + endOffset : 0, + collapsed : true, + commonAncestorContainer : d, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3 + }); + }; + + // Add range methods + extend(Range.prototype, { + setStart : function(n, o) { + this._setEndPoint(true, n, o); + }, + + setEnd : function(n, o) { + this._setEndPoint(false, n, o); + }, + + setStartBefore : function(n) { + this.setStart(n.parentNode, nodeIndex(n)); + }, + + setStartAfter : function(n) { + this.setStart(n.parentNode, nodeIndex(n) + 1); + }, + + setEndBefore : function(n) { + this.setEnd(n.parentNode, nodeIndex(n)); + }, + + setEndAfter : function(n) { + this.setEnd(n.parentNode, nodeIndex(n) + 1); + }, + + collapse : function(ts) { + var t = this; + + if (ts) { + t.endContainer = t.startContainer; + t.endOffset = t.startOffset; + } else { + t.startContainer = t.endContainer; + t.startOffset = t.endOffset; + } + + t.collapsed = true; + }, + + selectNode : function(n) { + this.setStartBefore(n); + this.setEndAfter(n); + }, + + selectNodeContents : function(n) { + this.setStart(n, 0); + this.setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }, + + compareBoundaryPoints : function(h, r) { + var t = this, sc = t.startContainer, so = t.startOffset, ec = t.endContainer, eo = t.endOffset; + + // Check START_TO_START + if (h === 0) + return t._compareBoundaryPoints(sc, so, sc, so); + + // Check START_TO_END + if (h === 1) + return t._compareBoundaryPoints(sc, so, ec, eo); + + // Check END_TO_END + if (h === 2) + return t._compareBoundaryPoints(ec, eo, ec, eo); + + // Check END_TO_START + if (h === 3) + return t._compareBoundaryPoints(ec, eo, sc, so); + }, + + deleteContents : function() { + this._traverse(DELETE); + }, + + extractContents : function() { + return this._traverse(EXTRACT); + }, + + cloneContents : function() { + return this._traverse(CLONE); + }, + + insertNode : function(n) { + var t = this, nn, o; + + // Node is TEXT_NODE or CDATA + if (n.nodeType === 3 || n.nodeType === 4) { + nn = t.startContainer.splitText(t.startOffset); + t.startContainer.parentNode.insertBefore(n, nn); + } else { + // Insert element node + if (t.startContainer.childNodes.length > 0) + o = t.startContainer.childNodes[t.startOffset]; + + t.startContainer.insertBefore(n, o); + } + }, + + surroundContents : function(n) { + var t = this, f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }, + + cloneRange : function() { + var t = this; + + return extend(new Range(t.dom), { + startContainer : t.startContainer, + startOffset : t.startOffset, + endContainer : t.endContainer, + endOffset : t.endOffset, + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }, + +/* + detach : function() { + // Not implemented + }, +*/ + // Internal methods + + _isCollapsed : function() { + return (this.startContainer == this.endContainer && this.startOffset == this.endOffset); + }, + + _compareBoundaryPoints : function (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 + } else if (offsetA < offsetB) { + return -1; // before + } else { + 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 + } else { + 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 + } else { + 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 = this.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; + } + }, + + _setEndPoint : function(st, n, o) { + var t = this, ec, sc; + + if (st) { + t.startContainer = n; + t.startOffset = o; + } else { + t.endContainer = n; + t.endOffset = 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.endContainer; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t.startContainer; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc != ec) { + t.collapse(st); + } else { + // 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 (t._compareBoundaryPoints(t.startContainer, t.startOffset, t.endContainer, t.endOffset) > 0) + t.collapse(st); + } + + t.collapsed = t._isCollapsed(); + t.commonAncestorContainer = t.dom.findCommonAncestor(t.startContainer, t.endContainer); + }, + + // This code is heavily "inspired" by the Apache Xerces implementation. I hope they don't mind. :) + + _traverse : function(how) { + var t = this, c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t.startContainer == t.endContainer) + return t._traverseSameContainer(how); + + for (c = t.endContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.startContainer) + return t._traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t.startContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.endContainer) + return t._traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t.startContainer; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t.endContainer; + 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 t._traverseCommonAncestors(startNode, endNode, how); + }, + + _traverseSameContainer : function(how) { + var t = this, frag, s, sub, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t.startOffset == t.endOffset) + return frag; + + // Text node needs special case handling + if (t.startContainer.nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t.startContainer.nodeValue; + sub = s.substring(t.startOffset, t.endOffset); + + // set the original text node to its new value + if (how != CLONE) { + t.startContainer.deleteData(t.startOffset, t.endOffset - t.startOffset); + + // Nothing is partially selected, so collapse to start point + t.collapse(true); + } + + if (how == DELETE) + return null; + + frag.appendChild(t.dom.doc.createTextNode(sub)); + return frag; + } + + // Copy nodes between the start/end offsets. + n = getSelectedNode(t.startContainer, t.startOffset); + cnt = t.endOffset - t.startOffset; + + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._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; + }, + + _traverseCommonStartContainer : function(endAncestor, how) { + var t = this, frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = indexOf(endAncestor, t.startContainer); + cnt = endIdx - t.startOffset; + + 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 = t._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; + }, + + _traverseCommonEndContainer : function(startAncestor, how) { + var t = this, frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = indexOf(startAncestor, t.endContainer); + ++startIdx; // Because we already traversed it.... + + cnt = t.endOffset - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseCommonAncestors : function(startAncestor, endAncestor, how) { + var t = this, n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = indexOf(startAncestor, commonParent); + endOffset = indexOf(endAncestor, commonParent); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = t._traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseRightBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.endContainer, t.endOffset - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent; + var isFullySelected = next != t.endContainer; + + if (next == root) + return t._traverseNode(next, isFullySelected, false, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, false, how); + + while (parent != null) { + while (next != null) { + prevSibling = next.previousSibling; + clonedChild = t._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 = t._traverseNode(parent, false, false, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseLeftBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.startContainer, t.startOffset); + var isFullySelected = next != t.startContainer, parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return t._traverseNode(next, isFullySelected, true, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, true, how); + + while (parent != null) { + while (next != null) { + nextSibling = next.nextSibling; + clonedChild = t._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 = t._traverseNode(parent, false, true, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseNode : function(n, isFullySelected, isLeft, how) { + var t = this, txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return t._traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t.startOffset; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t.endOffset; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return null; + + newNode = n.cloneNode(false); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return null; + + return n.cloneNode(false); + }, + + _traverseFullySelected : function(n, how) { + var t = this; + + if (how != DELETE) + return how == CLONE ? n.cloneNode(true) : n; + + n.parentNode.removeChild(n); + return null; + } + }); + + ns.Range = Range; +})(tinymce.dom); +(function() { + function Selection(selection) { + var t = this, invisibleChar = '\uFEFF', range, lastIERng; + + 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 1; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return 1; + } + + return 0; + }; + + function getRange() { + var dom = selection.dom, ieRange = selection.getRng(), domRange = dom.createRng(), startPos, endPos, element, sc, ec, collapsed; + + function findIndex(element) { + var nl = element.parentNode.childNodes, i; + + for (i = nl.length - 1; i >= 0; i--) { + if (nl[i] == element) + return i; + } + + return -1; + }; + + function findEndPoint(start) { + var rng = ieRange.duplicate(), parent, i, nl, n, offset = 0, index = 0, pos, tmpRng; + + // Insert marker character + rng.collapse(start); + parent = rng.parentElement(); + rng.pasteHTML(invisibleChar); // Needs to be a pasteHTML instead of .text = since IE has a bug with nodeValue + + // Find marker character + nl = parent.childNodes; + for (i = 0; i < nl.length; i++) { + n = nl[i]; + + // Calculate node index excluding text node fragmentation + if (i > 0 && (n.nodeType !== 3 || nl[i - 1].nodeType !== 3)) + index++; + + // If text node then calculate offset + if (n.nodeType === 3) { + // Look for marker + pos = n.nodeValue.indexOf(invisibleChar); + if (pos !== -1) { + offset += pos; + break; + } + + offset += n.nodeValue.length; + } else + offset = 0; + } + + // Remove marker character + rng.moveStart('character', -1); + rng.text = ''; + + return {index : index, offset : offset, parent : parent}; + }; + + // 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, findIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + // Check collapsed state + collapsed = selection.isCollapsed(); + + // Find start and end pos index and offset + startPos = findEndPoint(true); + endPos = findEndPoint(false); + + // Normalize the elements to avoid fragmented dom + startPos.parent.normalize(); + endPos.parent.normalize(); + + // Set start container and offset + sc = startPos.parent.childNodes[Math.min(startPos.index, startPos.parent.childNodes.length - 1)]; + + if (sc.nodeType != 3) + domRange.setStart(startPos.parent, startPos.index); + else + domRange.setStart(startPos.parent.childNodes[startPos.index], startPos.offset); + + // Set end container and offset + ec = endPos.parent.childNodes[Math.min(endPos.index, endPos.parent.childNodes.length - 1)]; + + if (ec.nodeType != 3) { + if (!collapsed) + endPos.index++; + + domRange.setEnd(endPos.parent, endPos.index); + } else + domRange.setEnd(endPos.parent.childNodes[endPos.index], endPos.offset); + + // If not collapsed then make sure offsets are valid + if (!collapsed) { + sc = domRange.startContainer; + if (sc.nodeType == 1) + domRange.setStart(sc, Math.min(domRange.startOffset, sc.childNodes.length)); + + ec = domRange.endContainer; + if (ec.nodeType == 1) + domRange.setEnd(ec, Math.min(domRange.endOffset, ec.childNodes.length)); + } + + // Restore selection to new range + t.addRange(domRange); + + return domRange; + }; + + this.addRange = function(rng) { + var ieRng, body = selection.dom.doc.body, startPos, endPos, sc, so, ec, eo; + + // Setup some shorter versions + sc = rng.startContainer; + so = rng.startOffset; + ec = rng.endContainer; + eo = rng.endOffset; + ieRng = body.createTextRange(); + + // Find element + sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc; + ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec; + + // 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(); + + return; + } + + function getCharPos(container, offset) { + var nodeVal, rng, pos; + + if (container.nodeType != 3) + return -1; + + nodeVal = container.nodeValue; + rng = body.createTextRange(); + + // Insert marker at offset position + container.nodeValue = nodeVal.substring(0, offset) + invisibleChar + nodeVal.substring(offset); + + // Find char pos of marker and remove it + rng.moveToElementText(container.parentNode); + rng.findText(invisibleChar); + pos = Math.abs(rng.moveStart('character', -0xFFFFF)); + container.nodeValue = nodeVal; + + return pos; + }; + + // Collapsed range + if (rng.collapsed) { + pos = getCharPos(sc, so); + + ieRng = body.createTextRange(); + ieRng.move('character', pos); + ieRng.select(); + + return; + } else { + // If same text container + if (sc == ec && sc.nodeType == 3) { + startPos = getCharPos(sc, so); + + ieRng.move('character', startPos); + ieRng.moveEnd('character', eo - so); + ieRng.select(); + + return; + } + + // Get caret positions + startPos = getCharPos(sc, so); + endPos = getCharPos(ec, eo); + ieRng = body.createTextRange(); + + // Move start of range to start character position or start element + if (startPos == -1) { + ieRng.moveToElementText(sc); + startPos = 0; + } else + ieRng.move('character', startPos); + + // Move end of range to end character position or end element + tmpRng = body.createTextRange(); + + if (endPos == -1) + tmpRng.moveToElementText(ec); + else + tmpRng.move('character', endPos); + + ieRng.setEndPoint('EndToEnd', tmpRng); + ieRng.select(); + + return; + } + }; + + 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; + }; + }; + + // 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 + // 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; + } + } + + }); + + // Shorten name and setup global instance + 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) { + var each = tinymce.each; + + tinymce.create('tinymce.dom.Element', { + Element : function(id, s) { + var t = this, dom, el; + + s = s || {}; + t.id = id; + t.dom = dom = s.dom || tinymce.DOM; + t.settings = s; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = t.dom.get(t.id); + + each([ + 'getPos', + 'getRect', + 'getParent', + 'add', + 'setStyle', + 'getStyle', + 'setStyles', + 'setAttrib', + 'setAttribs', + 'getAttrib', + 'addClass', + 'removeClass', + 'hasClass', + 'getOuterHTML', + 'setOuterHTML', + 'remove', + 'show', + 'hide', + 'isHidden', + 'setHTML', + 'get' + ], 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; + }; + }); + }, + + on : function(n, f, s) { + return tinymce.dom.Event.add(this.id, n, f, s); + }, + + getXY : function() { + return { + x : parseInt(this.getStyle('left')), + y : parseInt(this.getStyle('top')) + }; + }, + + getSize : function() { + var n = this.dom.get(this.id); + + return { + w : parseInt(this.getStyle('width') || n.clientWidth), + h : parseInt(this.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + this.setStyles({left : x, top : y}); + }, + + moveBy : function(x, y) { + var p = this.getXY(); + + this.moveTo(p.x + x, p.y + y); + }, + + resizeTo : function(w, h) { + this.setStyles({width : w, height : h}); + }, + + resizeBy : function(w, h) { + var s = this.getSize(); + + this.resizeTo(s.w + w, s.h + h); + }, + + update : function(k) { + var t = this, b, dom = t.dom; + + if (tinymce.isIE6 && t.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(t.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.setStyle(b, 'left', t.getStyle('left', 1)); + dom.setStyle(b, 'top', t.getStyle('top', 1)); + dom.setStyle(b, 'width', t.getStyle('width', 1)); + dom.setStyle(b, 'height', t.getStyle('height', 1)); + dom.setStyle(b, 'display', t.getStyle('display', 1)); + dom.setStyle(b, '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 + 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.setEndAfter(c); + t.setRng(r); + + // Delete the marker, and hopefully the caret gets placed in the right location + // Removed this since it seems to remove in FF and simply deleting it + // doesn't seem to affect the caret position in any browser + //d.execCommand('Delete', false, null); + + // 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 (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(1); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.firstChild; + + return e; + } else { + e = r.startContainer; + + if (e.nodeName == 'BODY') + return e.firstChild; + + return t.dom.getParent(e, '*'); + } + }, + + getEnd : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild; + + return e; + } else { + e = r.endContainer; + + if (e.nodeName == 'BODY') + return e.lastChild; + + return t.dom.getParent(e, '*'); + } + }, + + getBookmark : function(si) { + var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv; + sx = vp.x; + sy = vp.y; + + // Simple bookmark fast but not as persistent + if (si) + return {rng : r, scrollX : sx, scrollY : sy}; + + // Handle IE + if (isIE) { + // Control selection + if (r.item) { + e = r.item(0); + + each(t.dom.select(e.nodeName), function(n, i) { + if (e == n) { + sp = i; + return false; + } + }); + + return { + tag : e.nodeName, + index : sp, + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + tr = t.dom.doc.body.createTextRange(); + tr.moveToElementText(ro); + tr.collapse(true); + bp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(true); + sp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(false); + le = Math.abs(tr.move('character', c)) - sp; + + return { + start : sp - bp, + length : le, + scrollX : sx, + scrollY : sy + }; + } + + // Handle W3C + e = t.getNode(); + s = t.getSel(); + + if (!s) + return null; + + // Image selection + if (e && e.nodeName == 'IMG') { + return { + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + + function getPos(r, sn, en) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; + + while ((n = w.nextNode()) != null) { + if (n == sn) + d.start = p; + + if (n == en) { + d.end = p; + return d; + } + + p += trimNl(n.nodeValue || '').length; + } + + return null; + }; + + // Caret or selection + if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) { + e = getPos(ro, s.anchorNode, s.focusNode); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + // Count whitespace before + trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + + return { + start : Math.max(e.start + s.anchorOffset - wb, 0), + end : Math.max(e.end + s.focusOffset - wb, 0), + scrollX : sx, + scrollY : sy, + beg : s.anchorOffset - wb == 0 + }; + } else { + e = getPos(ro, r.startContainer, r.endContainer); + + // Count whitespace before start and end container + //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;}); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + return { + start : Math.max(e.start + r.startOffset - wb, 0), + end : Math.max(e.end + r.endOffset - wa, 0), + scrollX : sx, + scrollY : sy, + beg : r.startOffset - wb == 0 + }; + } + }, + + moveToBookmark : function(b) { + var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv; + + function getPos(r, sp, ep) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb; + + while ((n = w.nextNode()) != null) { + wa = wb = 0; + + nv = n.nodeValue || ''; + //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;}); + //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;}); + + nvl = trimNl(nv).length; + p += nvl; + + if (p >= sp && !d.startNode) { + o = sp - (p - nvl); + + // Fix for odd quirk in FF + if (b.beg && o >= nvl) + continue; + + d.startNode = n; + d.startOffset = o + wb; + } + + if (p >= ep) { + d.endNode = n; + d.endOffset = ep - (p - nvl) + wb; + return d; + } + } + + return null; + }; + + if (!b) + return false; + + t.win.scrollTo(b.scrollX, b.scrollY); + + // Handle explorer + if (isIE) { + // Handle simple + if (r = b.rng) { + try { + r.select(); + } catch (ex) { + // Ignore + } + + return true; + } + + t.win.focus(); + + // Handle control bookmark + if (b.tag) { + r = ro.createControlRange(); + + each(t.dom.select(b.tag), function(n, i) { + if (i == b.index) + r.addElement(n); + }); + } else { + // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs + try { + // Incorrect bookmark + if (b.start < 0) + return true; + + r = s.createRange(); + r.moveToElementText(ro); + r.collapse(true); + r.moveStart('character', b.start); + r.moveEnd('character', b.length); + } catch (ex2) { + return true; + } + } + + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + + return true; + } + + // Handle W3C + if (!s) + return false; + + // Handle simple + if (b.rng) { + s.removeAllRanges(); + s.addRange(b.rng); + } else { + if (is(b.start) && is(b.end)) { + try { + sd = getPos(ro, b.start, b.end); + + if (sd) { + r = t.dom.doc.createRange(); + r.setStart(sd.startNode, sd.startOffset); + r.setEnd(sd.endNode, sd.endOffset); + s.removeAllRanges(); + s.addRange(r); + } + + if (!tinymce.isOpera) + t.win.focus(); + } catch (ex) { + // Ignore + } + } + } + }, + + select : function(n, c) { + var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document; + + function find(n, start) { + var walker, o; + + if (n) { + walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + // Find first/last non empty text node + while (n = walker.nextNode()) { + o = n; + + if (tinymce.trim(n.nodeValue).length != 0) { + if (start) + return n; + else + o = n; + } + } + } + + return o; + }; + + if (isIE) { + try { + b = d.body; + + if (/^(IMG|TABLE)$/.test(n.nodeName)) { + r = b.createControlRange(); + r.addElement(n); + } else { + r = b.createTextRange(); + r.moveToElementText(n); + } + + r.select(); + } catch (ex) { + // Throws illigal agrument in IE some times + } + } else { + if (c) { + fn = find(n, 1) || t.dom.select('br:first', n)[0]; + ln = find(n, 0) || t.dom.select('br:last', n)[0]; + + if (fn && ln) { + r = d.createRange(); + + if (fn.nodeName == 'BR') + r.setStartBefore(fn); + else + r.setStart(fn, 0); + + if (ln.nodeName == 'BR') + r.setEndBefore(ln); + else + r.setEnd(ln, ln.nodeValue.length); + } else + r.selectNode(n); + } else + r.selectNode(n); + + t.setRng(r); + } + + return n; + }, + + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + return !s || r.boundingWidth == 0 || 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 = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange(); + + 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, r = t.getRng(), s = t.getSel(), e; + + if (!isIE) { + // Range maybe lost after the editor is made visible again + if (!r) + return t.dom.getRoot(); + + e = r.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!r.collapsed) { + // If the anchor node is a element instead of a text node then return this element + if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1) + return s.anchorNode.childNodes[s.anchorOffset]; + + if (r.startContainer == r.endContainer) { + if (r.startOffset - r.endOffset < 2) { + if (r.startContainer.hasChildNodes()) + e = r.startContainer.childNodes[r.startOffset]; + } + } + } + + return t.dom.getParent(e, '*'); + } + + return r.item ? r.item(0) : r.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('' + n + '>'); + + if (this.settings.indentation > 0) + this.writeRaw('\n'); + } + }, + + writeFullEndElement : function() { + if (this.tags.length > 0) { + this._writeAttributesEnd(); + this.writeRaw('' + this.tags.pop() + '>'); + + 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', + bool_attrs : /(checked|disabled|readonly|selected|nowrap)/, + valid_elements : '*[*]', + extended_valid_elements : 0, + valid_child_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, + font_size_style_values : 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; + + 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 '' + c + '>'; + + 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 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 ]*>)(.*?)(<\/script>)/g}, + {pattern : /(]*>)(.*?)(<\/noscript>)/g}, + {pattern : /("});j=j.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(h,l,k){return""})}j=j.replace(//g,"");j=j.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi,function(h,l){function k(o,n,q){var p=q;if(h.indexOf("mce_"+n)!=-1){return o}if(n=="style"){if(g._isRes(q)){return o}if(i.hex_colors){p=p.replace(/rgb\([^\)]+\)/g,function(m){return g.toHex(m)})}if(i.url_converter){p=p.replace(/url\([\'\"]?([^\)\'\"]+)\)/g,function(m,r){return"url("+g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(r),n,l))+")"})}}else{if(n!="coords"&&n!="shape"){if(i.url_converter){p=g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(q),n,l))}}}return" "+n+'="'+q+'" mce_'+n+'="'+p+'"'}h=h.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi,k);h=h.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi,k);return h.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi,k)})}return j},getOuterHTML:function(f){var g;f=this.get(f);if(!f){return null}if(f.outerHTML!==undefined){return f.outerHTML}g=(f.ownerDocument||this.doc).createElement("body");g.appendChild(f.cloneNode(true));return g.innerHTML},setOuterHTML:function(i,g,j){var f=this;return this.run(i,function(h){var l,k;h=f.get(h);j=j||h.ownerDocument||f.doc;if(a&&h.nodeType==1){h.outerHTML=g}else{k=j.createElement("body");k.innerHTML=g;l=k.lastChild;while(l){f.insertAfter(l.cloneNode(true),h);l=l.previousSibling}f.remove(h)}})},decode:function(g){var h,i,f;if(/&[^;]+;/.test(g)){h=this.doc.createElement("div");h.innerHTML=g;i=h.firstChild;f="";if(i){do{f+=i.nodeValue}while(i.nextSibling)}return f||g}return g},encode:function(f){return f?(""+f).replace(/[<>&\"]/g,function(h,g){switch(h){case"&":return"&";case'"':return""";case"<":return"<";case">":return">"}return h}):f},insertAfter:function(h,g){var f=this;g=f.get(g);return this.run(h,function(k){var j,i;j=g.parentNode;i=g.nextSibling;if(i){j.insertBefore(k,i)}else{j.appendChild(k)}return k})},isBlock:function(f){if(f.nodeType&&f.nodeType!==1){return false}f=f.nodeName||f;return/^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(f)},replace:function(i,h,f){var g=this;if(b(h,"array")){i=i.cloneNode(true)}return g.run(h,function(j){if(f){e(j.childNodes,function(k){i.appendChild(k.cloneNode(true))})}if(g.fixPsuedoLeaks&&j.nodeType===1){j.parentNode.insertBefore(i,j);g.remove(j);return i}return j.parentNode.replaceChild(i,j)})},findCommonAncestor:function(h,f){var i=h,g;while(i){g=f;while(g&&i!=g){g=g.parentNode}if(i==g){break}i=i.parentNode}if(!i&&h.ownerDocument){return h.ownerDocument.documentElement}return i},toHex:function(f){var h=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(f);function g(i){i=parseInt(i).toString(16);return i.length>1?i:"0"+i}if(h){f="#"+g(h[1])+g(h[2])+g(h[3]);return f}return f},getClasses:function(){var l=this,g=[],k,m={},n=l.settings.class_filter,j;if(l.classes){return l.classes}function o(f){e(f.imports,function(i){o(i)});e(f.cssRules||f.rules,function(i){switch(i.type||1){case 1:if(i.selectorText){e(i.selectorText.split(","),function(p){p=p.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(p)||!/\.[\w\-]+$/.test(p)){return}j=p;p=p.replace(/.*\.([a-z0-9_\-]+).*/i,"$1");if(n&&!(p=n(p,j))){return}if(!m[p]){g.push({"class":p});m[p]=1}})}break;case 3:o(i.styleSheet);break}})}try{e(l.doc.styleSheets,o)}catch(h){}if(g.length>0){l.classes=g}return g},run:function(j,i,h){var g=this,k;if(g.doc&&typeof(j)==="string"){j=g.get(j)}if(!j){return false}h=h||this;if(!j.nodeType&&(j.length||j.length===0)){k=[];e(j,function(l,f){if(l){if(typeof(l)=="string"){l=g.doc.getElementById(l)}k.push(i.call(h,l,f))}});return k}return i.call(h,j)},getAttribs:function(g){var f;g=this.get(g);if(!g){return[]}if(a){f=[];if(g.nodeName=="OBJECT"){return g.attributes}g.cloneNode(false).outerHTML.replace(/([a-z0-9\:\-_]+)=/gi,function(i,h){f.push({specified:1,nodeName:h})});return f}return g.attributes},destroy:function(g){var f=this;if(f.events){f.events.destroy()}f.win=f.doc=f.root=f.events=null;if(!g){c.removeUnload(f.destroy)}},createRng:function(){var f=this.doc;return f.createRange?f.createRange():new c.dom.Range(this)},split:function(k,j,n){var o=this,f=o.createRng(),l,i,m;function g(q,p){q=q[p];if(q&&q[p]&&q[p].nodeType==1&&h(q[p])){o.remove(q[p])}}function h(p){p=o.getOuterHTML(p);p=p.replace(/<(img|hr|table)/gi,"-");p=p.replace(/<[^>]+>/g,"");return p.replace(/[ \t\r\n]+| | /g,"")==""}if(k&&j){f.setStartBefore(k);f.setEndBefore(j);l=f.extractContents();f=o.createRng();f.setStartAfter(j);f.setEndAfter(k);i=f.extractContents();m=k.parentNode;g(l,"lastChild");if(!h(l)){m.insertBefore(l,k)}if(n){m.replaceChild(n,j)}else{m.insertBefore(j,k)}g(i,"firstChild");if(!h(i)){m.insertBefore(i,k)}o.remove(k);return n||j}},bind:function(j,f,i,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.add(j,f,i,h||this)},unbind:function(i,f,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.remove(i,f,h)},_isRes:function(f){return/^(top|left|bottom|right|width|height)/i.test(f)||/;\s*(top|left|bottom|right|width|height)/i.test(f)}});c.DOM=new c.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(f){var h=0,c=1,e=2,d=tinymce.extend;function g(m,k){var j,l;if(m.parentNode!=k){return -1}for(l=k.firstChild,j=0;l!=m;l=l.nextSibling){j++}return j}function b(k){var j=0;while(k.previousSibling){j++;k=k.previousSibling}return j}function i(j,k){var l;if(j.nodeType==3){return j}if(k<0){return j}l=j.firstChild;while(l!=null&&k>0){--k;l=l.nextSibling}if(l!=null){return l}return j}function a(k){var j=k.doc;d(this,{dom:k,startContainer:j,startOffset:0,endContainer:j,endOffset:0,collapsed:true,commonAncestorContainer:j,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3})}d(a.prototype,{setStart:function(k,j){this._setEndPoint(true,k,j)},setEnd:function(k,j){this._setEndPoint(false,k,j)},setStartBefore:function(j){this.setStart(j.parentNode,b(j))},setStartAfter:function(j){this.setStart(j.parentNode,b(j)+1)},setEndBefore:function(j){this.setEnd(j.parentNode,b(j))},setEndAfter:function(j){this.setEnd(j.parentNode,b(j)+1)},collapse:function(k){var j=this;if(k){j.endContainer=j.startContainer;j.endOffset=j.startOffset}else{j.startContainer=j.endContainer;j.startOffset=j.endOffset}j.collapsed=true},selectNode:function(j){this.setStartBefore(j);this.setEndAfter(j)},selectNodeContents:function(j){this.setStart(j,0);this.setEnd(j,j.nodeType===1?j.childNodes.length:j.nodeValue.length)},compareBoundaryPoints:function(m,n){var l=this,p=l.startContainer,o=l.startOffset,k=l.endContainer,j=l.endOffset;if(m===0){return l._compareBoundaryPoints(p,o,p,o)}if(m===1){return l._compareBoundaryPoints(p,o,k,j)}if(m===2){return l._compareBoundaryPoints(k,j,k,j)}if(m===3){return l._compareBoundaryPoints(k,j,p,o)}},deleteContents:function(){this._traverse(e)},extractContents:function(){return this._traverse(h)},cloneContents:function(){return this._traverse(c)},insertNode:function(m){var j=this,l,k;if(m.nodeType===3||m.nodeType===4){l=j.startContainer.splitText(j.startOffset);j.startContainer.parentNode.insertBefore(m,l)}else{if(j.startContainer.childNodes.length>0){k=j.startContainer.childNodes[j.startOffset]}j.startContainer.insertBefore(m,k)}},surroundContents:function(l){var j=this,k=j.extractContents();j.insertNode(l);l.appendChild(k);j.selectNode(l)},cloneRange:function(){var j=this;return d(new a(j.dom),{startContainer:j.startContainer,startOffset:j.startOffset,endContainer:j.endContainer,endOffset:j.endOffset,collapsed:j.collapsed,commonAncestorContainer:j.commonAncestorContainer})},_isCollapsed:function(){return(this.startContainer==this.endContainer&&this.startOffset==this.endOffset)},_compareBoundaryPoints:function(m,p,k,o){var q,l,j,r,t,s;if(m==k){if(p==o){return 0}else{if(p0){l.collapse(k)}}l.collapsed=l._isCollapsed();l.commonAncestorContainer=l.dom.findCommonAncestor(l.startContainer,l.endContainer)},_traverse:function(r){var s=this,q,m=0,v=0,k,o,l,n,j,u;if(s.startContainer==s.endContainer){return s._traverseSameContainer(r)}for(q=s.endContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.startContainer){return s._traverseCommonStartContainer(q,r)}++m}for(q=s.startContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.endContainer){return s._traverseCommonEndContainer(q,r)}++v}o=v-m;l=s.startContainer;while(o>0){l=l.parentNode;o--}n=s.endContainer;while(o<0){n=n.parentNode;o++}for(j=l.parentNode,u=n.parentNode;j!=u;j=j.parentNode,u=u.parentNode){l=j;n=u}return s._traverseCommonAncestors(l,n,r)},_traverseSameContainer:function(o){var r=this,q,u,j,k,l,p,m;if(o!=e){q=r.dom.doc.createDocumentFragment()}if(r.startOffset==r.endOffset){return q}if(r.startContainer.nodeType==3){u=r.startContainer.nodeValue;j=u.substring(r.startOffset,r.endOffset);if(o!=c){r.startContainer.deleteData(r.startOffset,r.endOffset-r.startOffset);r.collapse(true)}if(o==e){return null}q.appendChild(r.dom.doc.createTextNode(j));return q}k=i(r.startContainer,r.startOffset);l=r.endOffset-r.startOffset;while(l>0){p=k.nextSibling;m=r._traverseFullySelected(k,o);if(q){q.appendChild(m)}--l;k=p}if(o!=c){r.collapse(true)}return q},_traverseCommonStartContainer:function(j,p){var s=this,r,k,l,m,q,o;if(p!=e){r=s.dom.doc.createDocumentFragment()}k=s._traverseRightBoundary(j,p);if(r){r.appendChild(k)}l=g(j,s.startContainer);m=l-s.startOffset;if(m<=0){if(p!=c){s.setEndBefore(j);s.collapse(false)}return r}k=j.previousSibling;while(m>0){q=k.previousSibling;o=s._traverseFullySelected(k,p);if(r){r.insertBefore(o,r.firstChild)}--m;k=q}if(p!=c){s.setEndBefore(j);s.collapse(false)}return r},_traverseCommonEndContainer:function(m,p){var s=this,r,o,j,k,q,l;if(p!=e){r=s.dom.doc.createDocumentFragment()}j=s._traverseLeftBoundary(m,p);if(r){r.appendChild(j)}o=g(m,s.endContainer);++o;k=s.endOffset-o;j=m.nextSibling;while(k>0){q=j.nextSibling;l=s._traverseFullySelected(j,p);if(r){r.appendChild(l)}--k;j=q}if(p!=c){s.setStartAfter(m);s.collapse(true)}return r},_traverseCommonAncestors:function(p,j,s){var w=this,l,v,o,q,r,k,u,m;if(s!=e){v=w.dom.doc.createDocumentFragment()}l=w._traverseLeftBoundary(p,s);if(v){v.appendChild(l)}o=p.parentNode;q=g(p,o);r=g(j,o);++q;k=r-q;u=p.nextSibling;while(k>0){m=u.nextSibling;l=w._traverseFullySelected(u,s);if(v){v.appendChild(l)}u=m;--k}l=w._traverseRightBoundary(j,s);if(v){v.appendChild(l)}if(s!=c){w.setStartAfter(p);w.collapse(true)}return v},_traverseRightBoundary:function(p,q){var s=this,l=i(s.endContainer,s.endOffset-1),r,o,n,j,k;var m=l!=s.endContainer;if(l==p){return s._traverseNode(l,m,false,q)}r=l.parentNode;o=s._traverseNode(r,false,false,q);while(r!=null){while(l!=null){n=l.previousSibling;j=s._traverseNode(l,m,false,q);if(q!=e){o.insertBefore(j,o.firstChild)}m=true;l=n}if(r==p){return o}l=r.previousSibling;r=r.parentNode;k=s._traverseNode(r,false,false,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseLeftBoundary:function(p,q){var s=this,m=i(s.startContainer,s.startOffset);var n=m!=s.startContainer,r,o,l,j,k;if(m==p){return s._traverseNode(m,n,true,q)}r=m.parentNode;o=s._traverseNode(r,false,true,q);while(r!=null){while(m!=null){l=m.nextSibling;j=s._traverseNode(m,n,true,q);if(q!=e){o.appendChild(j)}n=true;m=l}if(r==p){return o}m=r.nextSibling;r=r.parentNode;k=s._traverseNode(r,false,true,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseNode:function(j,o,r,s){var u=this,m,l,p,k,q;if(o){return u._traverseFullySelected(j,s)}if(j.nodeType==3){m=j.nodeValue;if(r){k=u.startOffset;l=m.substring(k);p=m.substring(0,k)}else{k=u.endOffset;l=m.substring(0,k);p=m.substring(k)}if(s!=c){j.nodeValue=p}if(s==e){return null}q=j.cloneNode(false);q.nodeValue=l;return q}if(s==e){return null}return j.cloneNode(false)},_traverseFullySelected:function(l,k){var j=this;if(k!=e){return k==c?l.cloneNode(true):l}l.parentNode.removeChild(l);return null}});f.Range=a})(tinymce.dom);(function(){function a(e){var d=this,h="\uFEFF",b,g;function c(j,i){if(j&&i){if(j.item&&i.item&&j.item(0)===i.item(0)){return 1}if(j.isEqual&&i.isEqual&&i.isEqual(j)){return 1}}return 0}function f(){var m=e.dom,j=e.getRng(),s=m.createRng(),p,k,n,q,o,l;function i(v){var t=v.parentNode.childNodes,u;for(u=t.length-1;u>=0;u--){if(t[u]==v){return u}}return -1}function r(v){var t=j.duplicate(),B,y,u,w,x=0,z=0,A,C;t.collapse(v);B=t.parentElement();t.pasteHTML(h);u=B.childNodes;for(y=0;y0&&(w.nodeType!==3||u[y-1].nodeType!==3)){z++}if(w.nodeType===3){A=w.nodeValue.indexOf(h);if(A!==-1){x+=A;break}x+=w.nodeValue.length}else{x=0}}t.moveStart("character",-1);t.text="";return{index:z,offset:x,parent:B}}n=j.item?j.item(0):j.parentElement();if(n.ownerDocument!=m.doc){return s}if(j.item||!n.hasChildNodes()){s.setStart(n.parentNode,i(n));s.setEnd(s.startContainer,s.startOffset+1);return s}l=e.isCollapsed();p=r(true);k=r(false);p.parent.normalize();k.parent.normalize();q=p.parent.childNodes[Math.min(p.index,p.parent.childNodes.length-1)];if(q.nodeType!=3){s.setStart(p.parent,p.index)}else{s.setStart(p.parent.childNodes[p.index],p.offset)}o=k.parent.childNodes[Math.min(k.index,k.parent.childNodes.length-1)];if(o.nodeType!=3){if(!l){k.index++}s.setEnd(k.parent,k.index)}else{s.setEnd(k.parent.childNodes[k.index],k.offset)}if(!l){q=s.startContainer;if(q.nodeType==1){s.setStart(q,Math.min(s.startOffset,q.childNodes.length))}o=s.endContainer;if(o.nodeType==1){s.setEnd(o,Math.min(s.endOffset,o.childNodes.length))}}d.addRange(s);return s}this.addRange=function(j){var o,m=e.dom.doc.body,p,k,q,l,n,i;q=j.startContainer;l=j.startOffset;n=j.endContainer;i=j.endOffset;o=m.createTextRange();q=q.nodeType==1?q.childNodes[Math.min(l,q.childNodes.length-1)]:q;n=n.nodeType==1?n.childNodes[Math.min(l==i?i:i-1,n.childNodes.length-1)]:n;if(q==n&&q.nodeType==1){if(/^(IMG|TABLE)$/.test(q.nodeName)&&l!=i){o=m.createControlRange();o.addElement(q)}else{o=m.createTextRange();if(!q.hasChildNodes()&&q.canHaveHTML){q.innerHTML=h}o.moveToElementText(q);if(q.innerHTML==h){o.collapse(true);q.removeChild(q.firstChild)}}if(l==i){o.collapse(i<=j.endContainer.childNodes.length-1)}o.select();return}function r(t,v){var u,s,w;if(t.nodeType!=3){return -1}u=t.nodeValue;s=m.createTextRange();t.nodeValue=u.substring(0,v)+h+u.substring(v);s.moveToElementText(t.parentNode);s.findText(h);w=Math.abs(s.moveStart("character",-1048575));t.nodeValue=u;return w}if(j.collapsed){pos=r(q,l);o=m.createTextRange();o.move("character",pos);o.select();return}else{if(q==n&&q.nodeType==3){p=r(q,l);o.move("character",p);o.moveEnd("character",i-l);o.select();return}p=r(q,l);k=r(n,i);o=m.createTextRange();if(p==-1){o.moveToElementText(q);p=0}else{o.move("character",p)}tmpRng=m.createTextRange();if(k==-1){tmpRng.moveToElementText(n)}else{tmpRng.move("character",k)}o.setEndPoint("EndToEnd",tmpRng);o.select();return}};this.getRangeAt=function(){if(!b||!c(g,e.getRng())){b=f();g=e.getRng()}return b};this.destroy=function(){g=b=null}}tinymce.dom.TridentSelection=a})();(function(){var p=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,i=0,d=Object.prototype.toString,n=false;var b=function(D,t,A,v){A=A||[];var e=t=t||document;if(t.nodeType!==1&&t.nodeType!==9){return[]}if(!D||typeof D!=="string"){return A}var B=[],C,y,G,F,z,s,r=true,w=o(t);p.lastIndex=0;while((C=p.exec(D))!==null){B.push(C[1]);if(C[2]){s=RegExp.rightContext;break}}if(B.length>1&&j.exec(D)){if(B.length===2&&f.relative[B[0]]){y=g(B[0]+B[1],t)}else{y=f.relative[B[0]]?[t]:b(B.shift(),t);while(B.length){D=B.shift();if(f.relative[D]){D+=B.shift()}y=g(D,y)}}}else{if(!v&&B.length>1&&t.nodeType===9&&!w&&f.match.ID.test(B[0])&&!f.match.ID.test(B[B.length-1])){var H=b.find(B.shift(),t,w);t=H.expr?b.filter(H.expr,H.set)[0]:H.set[0]}if(t){var H=v?{expr:B.pop(),set:a(v)}:b.find(B.pop(),B.length===1&&(B[0]==="~"||B[0]==="+")&&t.parentNode?t.parentNode:t,w);y=H.expr?b.filter(H.expr,H.set):H.set;if(B.length>0){G=a(y)}else{r=false}while(B.length){var u=B.pop(),x=u;if(!f.relative[u]){u=""}else{x=B.pop()}if(x==null){x=t}f.relative[u](G,x,w)}}else{G=B=[]}}if(!G){G=y}if(!G){throw"Syntax error, unrecognized expression: "+(u||D)}if(d.call(G)==="[object Array]"){if(!r){A.push.apply(A,G)}else{if(t&&t.nodeType===1){for(var E=0;G[E]!=null;E++){if(G[E]&&(G[E]===true||G[E].nodeType===1&&h(t,G[E]))){A.push(y[E])}}}else{for(var E=0;G[E]!=null;E++){if(G[E]&&G[E].nodeType===1){A.push(y[E])}}}}}else{a(G,A)}if(s){b(s,e,A,v);b.uniqueSort(A)}return A};b.uniqueSort=function(r){if(c){n=false;r.sort(c);if(n){for(var e=1;e":function(w,r,x){var u=typeof r==="string";if(u&&!/\W/.test(r)){r=x?r:r.toUpperCase();for(var s=0,e=w.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,w){var t=u[1].replace(/\\/g,"");if(!w&&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(w,s,t,x){var r=s[1],u=f.filters[r];if(u){return u(w,t,s,x)}else{if(r==="contains"){return(w.textContent||w.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),w=e+"",u=t[2],r=t[4];return e==null?u==="!=":u==="="?w===r:u==="*="?w.indexOf(r)>=0:u==="~="?(" "+w+" ").indexOf(r)>=0:!r?w&&e!==false:u==="!="?w!=r:u==="^="?w.indexOf(r)===0:u==="$="?w.substr(w.length-r.length)===r:u==="|="?w===r||w.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,w){if(typeof v.getElementById!=="undefined"&&!w){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(w,v,t,u){v=v||document;if(!u&&v.nodeType===9&&!o(v)){try{return a(v.querySelectorAll(w),t)}catch(x){}}return e(w,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,w,v,A,x,z){var y=r=="previousSibling"&&!z;for(var t=0,s=A.length;t0){u=e;break}}}e=e[r]}A[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,x){var t=[],u="",v,s=x.nodeType?[x]:x;while((v=f.match.PSEUDO.exec(e))){u+=v[0];e=e.replace(f.match.PSEUDO,"")}e=f.relative[e]?e+"*":e;for(var w=0,r=s.length;w=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){var b=a.each;a.create("tinymce.dom.Element",{Element:function(g,e){var c=this,f,d;e=e||{};c.id=g;c.dom=f=e.dom||a.DOM;c.settings=e;if(!a.isIE){d=c.dom.get(c.id)}b(["getPos","getRect","getParent","add","setStyle","getStyle","setStyles","setAttrib","setAttribs","getAttrib","addClass","removeClass","hasClass","getOuterHTML","setOuterHTML","remove","show","hide","isHidden","setHTML","get"],function(h){c[h]=function(){var j=[g],k;for(k=0;k_';j.deleteContents();j.insertNode(f.getRng().createContextualFragment(i));l=f.dom.get("__caret");j=k.createRange();j.setStartBefore(l);j.setEndAfter(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(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(1);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.firstChild}return h}else{h=g.startContainer;if(h.nodeName=="BODY"){return h.firstChild}return f.dom.getParent(h,"*")}},getEnd:function(){var f=this,g=f.getRng(),h;if(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(0);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.lastChild}return h}else{h=g.endContainer;if(h.nodeName=="BODY"){return h.lastChild}return f.dom.getParent(h,"*")}},getBookmark:function(x){var j=this,m=j.getRng(),f,n,l,u=j.dom.getViewPort(j.win),v,p,z,o,w=-16777215,k,h=j.dom.getRoot(),g=0,i=0,y;n=u.x;l=u.y;if(x){return{rng:m,scrollX:n,scrollY:l}}if(a){if(m.item){v=m.item(0);d(j.dom.select(v.nodeName),function(s,r){if(v==s){p=r;return false}});return{tag:v.nodeName,index:p,scrollX:n,scrollY:l}}f=j.dom.doc.body.createTextRange();f.moveToElementText(h);f.collapse(true);z=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(true);p=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(false);o=Math.abs(f.move("character",w))-p;return{start:p-z,length:o,scrollX:n,scrollY:l}}v=j.getNode();k=j.getSel();if(!k){return null}if(v&&v.nodeName=="IMG"){return{scrollX:n,scrollY:l}}function q(A,D,t){var s=j.dom.doc.createTreeWalker(A,NodeFilter.SHOW_TEXT,null,false),E,B=0,C={};while((E=s.nextNode())!=null){if(E==D){C.start=B}if(E==t){C.end=B;return C}B+=e(E.nodeValue||"").length}return null}if(k.anchorNode==k.focusNode&&k.anchorOffset==k.focusOffset){v=q(h,k.anchorNode,k.focusNode);if(!v){return{scrollX:n,scrollY:l}}e(k.anchorNode.nodeValue||"").replace(/^\s+/,function(r){g=r.length});return{start:Math.max(v.start+k.anchorOffset-g,0),end:Math.max(v.end+k.focusOffset-g,0),scrollX:n,scrollY:l,beg:k.anchorOffset-g==0}}else{v=q(h,m.startContainer,m.endContainer);if(!v){return{scrollX:n,scrollY:l}}return{start:Math.max(v.start+m.startOffset-g,0),end:Math.max(v.end+m.endOffset-i,0),scrollX:n,scrollY:l,beg:m.startOffset-g==0}}},moveToBookmark:function(n){var o=this,g=o.getRng(),p=o.getSel(),j=o.dom.getRoot(),m,h,k;function i(q,t,D){var B=o.dom.doc.createTreeWalker(q,NodeFilter.SHOW_TEXT,null,false),x,s=0,A={},u,C,z,y;while((x=B.nextNode())!=null){z=y=0;k=x.nodeValue||"";h=e(k).length;s+=h;if(s>=t&&!A.startNode){u=t-(s-h);if(n.beg&&u>=h){continue}A.startNode=x;A.startOffset=u+y}if(s>=D){A.endNode=x;A.endOffset=D-(s-h)+y;return A}}return null}if(!n){return false}o.win.scrollTo(n.scrollX,n.scrollY);if(a){if(g=n.rng){try{g.select()}catch(l){}return true}o.win.focus();if(n.tag){g=j.createControlRange();d(o.dom.select(n.tag),function(r,q){if(q==n.index){g.addElement(r)}})}else{try{if(n.start<0){return true}g=p.createRange();g.moveToElementText(j);g.collapse(true);g.moveStart("character",n.start);g.moveEnd("character",n.length)}catch(f){return true}}try{g.select()}catch(l){}return true}if(!p){return false}if(n.rng){p.removeAllRanges();p.addRange(n.rng)}else{if(b(n.start)&&b(n.end)){try{m=i(j,n.start,n.end);if(m){g=o.dom.doc.createRange();g.setStart(m.startNode,m.startOffset);g.setEnd(m.endNode,m.endOffset);p.removeAllRanges();p.addRange(g)}if(!c.isOpera){o.win.focus()}}catch(l){}}}},select:function(g,l){var p=this,f=p.getRng(),q=p.getSel(),o,m,k,j=p.win.document;function h(u,t){var s,r;if(u){s=j.createTreeWalker(u,NodeFilter.SHOW_TEXT,null,false);while(u=s.nextNode()){r=u;if(c.trim(u.nodeValue).length!=0){if(t){return u}else{r=u}}}}return r}if(a){try{o=j.body;if(/^(IMG|TABLE)$/.test(g.nodeName)){f=o.createControlRange();f.addElement(g)}else{f=o.createTextRange();f.moveToElementText(g)}f.select()}catch(i){}}else{if(l){m=h(g,1)||p.dom.select("br:first",g)[0];k=h(g,0)||p.dom.select("br:last",g)[0];if(m&&k){f=j.createRange();if(m.nodeName=="BR"){f.setStartBefore(m)}else{f.setStart(m,0)}if(k.nodeName=="BR"){f.setEndBefore(k)}else{f.setEnd(k,k.nodeValue.length)}}else{f.selectNode(g)}}else{f.selectNode(g)}p.setRng(f)}return g},isCollapsed:function(){var f=this,h=f.getRng(),g=f.getSel();if(!h||h.item){return false}return !g||h.boundingWidth==0||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=a?g.win.document.body.createTextRange():g.win.document.createRange()}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 f=this,h=f.getRng(),g=f.getSel(),i;if(!a){if(!h){return f.dom.getRoot()}i=h.commonAncestorContainer;if(!h.collapsed){if(c.isWebKit&&g.anchorNode&&g.anchorNode.nodeType==1){return g.anchorNode.childNodes[g.anchorOffset]}if(h.startContainer==h.endContainer){if(h.startOffset-h.endOffset<2){if(h.startContainer.hasChildNodes()){i=h.startContainer.childNodes[h.startOffset]}}}}return f.dom.getParent(i,"*")}return h.item?h.item(0):h.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(""+b+">")}if(this.settings.indentation>0){this.writeRaw("\n")}}},writeFullEndElement:function(){if(this.tags.length>0){this._writeAttributesEnd();this.writeRaw(""+this.tags.pop()+">");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",bool_attrs:/(checked|disabled|readonly|selected|nowrap)/,valid_elements:"*[*]",extended_valid_elements:0,valid_child_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,font_size_style_values: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;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""+o+">"}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,y,w=["ol","ul"],u,t,q,k=/^(OL|UL)$/,z;function m(r,x){var o=x.split(","),p;while((r=r.previousSibling)!=null){for(p=0;p1){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]*>)(.*?)(<\/script>)/g},{pattern:/(]*>)(.*?)(<\/noscript>)/g},{pattern:/('; + }); + + // Wrap noscript elements + h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { + return ''; + }); + } + + h = h.replace(//g, ''); + + // Process all tags with src, href or style + h = h.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi, function(a, n) { + function handle(m, b, c) { + var u = c; + + // Tag already got a mce_ version + if (a.indexOf('mce_' + b) != -1) + return m; + + if (b == 'style') { + // No mce_style for elements with these since they might get resized by the user + if (t._isRes(c)) + return m; + + if (s.hex_colors) { + u = u.replace(/rgb\([^\)]+\)/g, function(v) { + return t.toHex(v); + }); + } + + if (s.url_converter) { + u = u.replace(/url\([\'\"]?([^\)\'\"]+)\)/g, function(x, c) { + return 'url(' + t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)) + ')'; + }); + } + } else if (b != 'coords' && b != 'shape') { + if (s.url_converter) + u = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)); + } + + return ' ' + b + '="' + c + '" mce_' + b + '="' + u + '"'; + }; + + a = a.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi, handle); // W3C + a = a.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi, handle); // W3C + + return a.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi, handle); // IE + }); + } + + 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; + + return this.run(e, function(e) { + var n, tp; + + e = t.get(e); + d = d || e.ownerDocument || t.doc; + + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else { + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + } + }); + }, + + decode : function(s) { + var e, n, v; + + // Look for entities to decode + if (/&[^;]+;/.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.nextSibling); + } + + return v || s; + } + + return s; + }, + + encode : function(s) { + return s ? ('' + s).replace(/[<>&\"]/g, function (c, b) { + switch (c) { + case '&': + return '&'; + + case '"': + return '"'; + + case '<': + return '<'; + + case '>': + return '>'; + } + + return c; + }) : s; + }, + + insertAfter : function(n, r) { + var t = this; + + r = t.get(r); + + return this.run(n, function(n) { + var p, ns; + + p = r.parentNode; + ns = r.nextSibling; + + if (ns) + p.insertBefore(n, ns); + else + p.appendChild(n); + + return n; + }); + }, + + isBlock : function(n) { + if (n.nodeType && n.nodeType !== 1) + return false; + + n = n.nodeName || n; + + return /^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.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(o.childNodes, function(c) { + n.appendChild(c.cloneNode(true)); + }); + } + + // Fix IE psuedo leak for elements since replacing elements if fairly common + // Will break parentNode for some unknown reason + if (t.fixPsuedoLeaks && o.nodeType === 1) { + o.parentNode.insertBefore(n, o); + t.remove(o); + return n; + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + 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; + + // 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(/([a-z0-9\:\-_]+)=/gi, function(a, b) { + o.push({specified : 1, nodeName : b}); + }); + + 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); + }, + + 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 sence + // but we don't want that in our code since it serves no purpose + // For example if this is chopped: + // text 1CHOPtext 2 + // would produce: + // text 1CHOPtext 2 + // this function will then trim of empty edges and produce: + // text 1CHOPtext 2 + function trimEdge(n, na) { + n = n[na]; + + if (n && n[na] && n[na].nodeType == 1 && isEmpty(n[na])) + t.remove(n[na]); + }; + + function isEmpty(n) { + n = t.getOuterHTML(n); + n = n.replace(/<(img|hr|table)/gi, '-'); // Keep these convert them to - chars + n = n.replace(/<[^>]+>/g, ''); // Remove all tags + + return n.replace(/[ \t\r\n]+| | /g, '') == ''; + }; + + if (pe && e) { + // Get before chunk + r.setStartBefore(pe); + r.setEndBefore(e); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStartAfter(e); + r.setEndAfter(pe); + aft = r.extractContents(); + + // Insert chunks and remove parent + pa = pe.parentNode; + + // Remove right side edge of the before contents + trimEdge(bef, 'lastChild'); + + if (!isEmpty(bef)) + pa.insertBefore(bef, pe); + + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Remove left site edge of the after contents + trimEdge(aft, 'firstChild'); + + if (!isEmpty(aft)) + pa.insertBefore(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); + }, + + + _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; + } + */ + + }); + + // Setup page DOM + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); +(function(ns) { + // Traverse constants + var EXTRACT = 0, CLONE = 1, DELETE = 2, extend = tinymce.extend; + + function indexOf(child, parent) { + var i, node; + + if (child.parentNode != parent) + return -1; + + for (node = parent.firstChild, i = 0; node != child; node = node.nextSibling) + i++; + + return i; + }; + + function nodeIndex(n) { + var i = 0; + + while (n.previousSibling) { + i++; + n = n.previousSibling; + } + + return i; + }; + + function getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child != null && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child != null) + return child; + + return container; + }; + + // Range constructor + function Range(dom) { + var d = dom.doc; + + extend(this, { + dom : dom, + + // Inital states + startContainer : d, + startOffset : 0, + endContainer : d, + endOffset : 0, + collapsed : true, + commonAncestorContainer : d, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3 + }); + }; + + // Add range methods + extend(Range.prototype, { + setStart : function(n, o) { + this._setEndPoint(true, n, o); + }, + + setEnd : function(n, o) { + this._setEndPoint(false, n, o); + }, + + setStartBefore : function(n) { + this.setStart(n.parentNode, nodeIndex(n)); + }, + + setStartAfter : function(n) { + this.setStart(n.parentNode, nodeIndex(n) + 1); + }, + + setEndBefore : function(n) { + this.setEnd(n.parentNode, nodeIndex(n)); + }, + + setEndAfter : function(n) { + this.setEnd(n.parentNode, nodeIndex(n) + 1); + }, + + collapse : function(ts) { + var t = this; + + if (ts) { + t.endContainer = t.startContainer; + t.endOffset = t.startOffset; + } else { + t.startContainer = t.endContainer; + t.startOffset = t.endOffset; + } + + t.collapsed = true; + }, + + selectNode : function(n) { + this.setStartBefore(n); + this.setEndAfter(n); + }, + + selectNodeContents : function(n) { + this.setStart(n, 0); + this.setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }, + + compareBoundaryPoints : function(h, r) { + var t = this, sc = t.startContainer, so = t.startOffset, ec = t.endContainer, eo = t.endOffset; + + // Check START_TO_START + if (h === 0) + return t._compareBoundaryPoints(sc, so, sc, so); + + // Check START_TO_END + if (h === 1) + return t._compareBoundaryPoints(sc, so, ec, eo); + + // Check END_TO_END + if (h === 2) + return t._compareBoundaryPoints(ec, eo, ec, eo); + + // Check END_TO_START + if (h === 3) + return t._compareBoundaryPoints(ec, eo, sc, so); + }, + + deleteContents : function() { + this._traverse(DELETE); + }, + + extractContents : function() { + return this._traverse(EXTRACT); + }, + + cloneContents : function() { + return this._traverse(CLONE); + }, + + insertNode : function(n) { + var t = this, nn, o; + + // Node is TEXT_NODE or CDATA + if (n.nodeType === 3 || n.nodeType === 4) { + nn = t.startContainer.splitText(t.startOffset); + t.startContainer.parentNode.insertBefore(n, nn); + } else { + // Insert element node + if (t.startContainer.childNodes.length > 0) + o = t.startContainer.childNodes[t.startOffset]; + + t.startContainer.insertBefore(n, o); + } + }, + + surroundContents : function(n) { + var t = this, f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }, + + cloneRange : function() { + var t = this; + + return extend(new Range(t.dom), { + startContainer : t.startContainer, + startOffset : t.startOffset, + endContainer : t.endContainer, + endOffset : t.endOffset, + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }, + +/* + detach : function() { + // Not implemented + }, +*/ + // Internal methods + + _isCollapsed : function() { + return (this.startContainer == this.endContainer && this.startOffset == this.endOffset); + }, + + _compareBoundaryPoints : function (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 + } else if (offsetA < offsetB) { + return -1; // before + } else { + 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 + } else { + 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 + } else { + 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 = this.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; + } + }, + + _setEndPoint : function(st, n, o) { + var t = this, ec, sc; + + if (st) { + t.startContainer = n; + t.startOffset = o; + } else { + t.endContainer = n; + t.endOffset = 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.endContainer; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t.startContainer; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc != ec) { + t.collapse(st); + } else { + // 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 (t._compareBoundaryPoints(t.startContainer, t.startOffset, t.endContainer, t.endOffset) > 0) + t.collapse(st); + } + + t.collapsed = t._isCollapsed(); + t.commonAncestorContainer = t.dom.findCommonAncestor(t.startContainer, t.endContainer); + }, + + // This code is heavily "inspired" by the Apache Xerces implementation. I hope they don't mind. :) + + _traverse : function(how) { + var t = this, c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t.startContainer == t.endContainer) + return t._traverseSameContainer(how); + + for (c = t.endContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.startContainer) + return t._traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t.startContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.endContainer) + return t._traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t.startContainer; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t.endContainer; + 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 t._traverseCommonAncestors(startNode, endNode, how); + }, + + _traverseSameContainer : function(how) { + var t = this, frag, s, sub, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t.startOffset == t.endOffset) + return frag; + + // Text node needs special case handling + if (t.startContainer.nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t.startContainer.nodeValue; + sub = s.substring(t.startOffset, t.endOffset); + + // set the original text node to its new value + if (how != CLONE) { + t.startContainer.deleteData(t.startOffset, t.endOffset - t.startOffset); + + // Nothing is partially selected, so collapse to start point + t.collapse(true); + } + + if (how == DELETE) + return null; + + frag.appendChild(t.dom.doc.createTextNode(sub)); + return frag; + } + + // Copy nodes between the start/end offsets. + n = getSelectedNode(t.startContainer, t.startOffset); + cnt = t.endOffset - t.startOffset; + + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._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; + }, + + _traverseCommonStartContainer : function(endAncestor, how) { + var t = this, frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = indexOf(endAncestor, t.startContainer); + cnt = endIdx - t.startOffset; + + 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 = t._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; + }, + + _traverseCommonEndContainer : function(startAncestor, how) { + var t = this, frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = indexOf(startAncestor, t.endContainer); + ++startIdx; // Because we already traversed it.... + + cnt = t.endOffset - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseCommonAncestors : function(startAncestor, endAncestor, how) { + var t = this, n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = indexOf(startAncestor, commonParent); + endOffset = indexOf(endAncestor, commonParent); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = t._traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseRightBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.endContainer, t.endOffset - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent; + var isFullySelected = next != t.endContainer; + + if (next == root) + return t._traverseNode(next, isFullySelected, false, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, false, how); + + while (parent != null) { + while (next != null) { + prevSibling = next.previousSibling; + clonedChild = t._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 = t._traverseNode(parent, false, false, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseLeftBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.startContainer, t.startOffset); + var isFullySelected = next != t.startContainer, parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return t._traverseNode(next, isFullySelected, true, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, true, how); + + while (parent != null) { + while (next != null) { + nextSibling = next.nextSibling; + clonedChild = t._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 = t._traverseNode(parent, false, true, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseNode : function(n, isFullySelected, isLeft, how) { + var t = this, txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return t._traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t.startOffset; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t.endOffset; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return null; + + newNode = n.cloneNode(false); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return null; + + return n.cloneNode(false); + }, + + _traverseFullySelected : function(n, how) { + var t = this; + + if (how != DELETE) + return how == CLONE ? n.cloneNode(true) : n; + + n.parentNode.removeChild(n); + return null; + } + }); + + ns.Range = Range; +})(tinymce.dom); +(function() { + function Selection(selection) { + var t = this, invisibleChar = '\uFEFF', range, lastIERng; + + 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 1; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return 1; + } + + return 0; + }; + + function getRange() { + var dom = selection.dom, ieRange = selection.getRng(), domRange = dom.createRng(), startPos, endPos, element, sc, ec, collapsed; + + function findIndex(element) { + var nl = element.parentNode.childNodes, i; + + for (i = nl.length - 1; i >= 0; i--) { + if (nl[i] == element) + return i; + } + + return -1; + }; + + function findEndPoint(start) { + var rng = ieRange.duplicate(), parent, i, nl, n, offset = 0, index = 0, pos, tmpRng; + + // Insert marker character + rng.collapse(start); + parent = rng.parentElement(); + rng.pasteHTML(invisibleChar); // Needs to be a pasteHTML instead of .text = since IE has a bug with nodeValue + + // Find marker character + nl = parent.childNodes; + for (i = 0; i < nl.length; i++) { + n = nl[i]; + + // Calculate node index excluding text node fragmentation + if (i > 0 && (n.nodeType !== 3 || nl[i - 1].nodeType !== 3)) + index++; + + // If text node then calculate offset + if (n.nodeType === 3) { + // Look for marker + pos = n.nodeValue.indexOf(invisibleChar); + if (pos !== -1) { + offset += pos; + break; + } + + offset += n.nodeValue.length; + } else + offset = 0; + } + + // Remove marker character + rng.moveStart('character', -1); + rng.text = ''; + + return {index : index, offset : offset, parent : parent}; + }; + + // 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, findIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + // Check collapsed state + collapsed = selection.isCollapsed(); + + // Find start and end pos index and offset + startPos = findEndPoint(true); + endPos = findEndPoint(false); + + // Normalize the elements to avoid fragmented dom + startPos.parent.normalize(); + endPos.parent.normalize(); + + // Set start container and offset + sc = startPos.parent.childNodes[Math.min(startPos.index, startPos.parent.childNodes.length - 1)]; + + if (sc.nodeType != 3) + domRange.setStart(startPos.parent, startPos.index); + else + domRange.setStart(startPos.parent.childNodes[startPos.index], startPos.offset); + + // Set end container and offset + ec = endPos.parent.childNodes[Math.min(endPos.index, endPos.parent.childNodes.length - 1)]; + + if (ec.nodeType != 3) { + if (!collapsed) + endPos.index++; + + domRange.setEnd(endPos.parent, endPos.index); + } else + domRange.setEnd(endPos.parent.childNodes[endPos.index], endPos.offset); + + // If not collapsed then make sure offsets are valid + if (!collapsed) { + sc = domRange.startContainer; + if (sc.nodeType == 1) + domRange.setStart(sc, Math.min(domRange.startOffset, sc.childNodes.length)); + + ec = domRange.endContainer; + if (ec.nodeType == 1) + domRange.setEnd(ec, Math.min(domRange.endOffset, ec.childNodes.length)); + } + + // Restore selection to new range + t.addRange(domRange); + + return domRange; + }; + + this.addRange = function(rng) { + var ieRng, body = selection.dom.doc.body, startPos, endPos, sc, so, ec, eo; + + // Setup some shorter versions + sc = rng.startContainer; + so = rng.startOffset; + ec = rng.endContainer; + eo = rng.endOffset; + ieRng = body.createTextRange(); + + // Find element + sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc; + ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec; + + // 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(); + + return; + } + + function getCharPos(container, offset) { + var nodeVal, rng, pos; + + if (container.nodeType != 3) + return -1; + + nodeVal = container.nodeValue; + rng = body.createTextRange(); + + // Insert marker at offset position + container.nodeValue = nodeVal.substring(0, offset) + invisibleChar + nodeVal.substring(offset); + + // Find char pos of marker and remove it + rng.moveToElementText(container.parentNode); + rng.findText(invisibleChar); + pos = Math.abs(rng.moveStart('character', -0xFFFFF)); + container.nodeValue = nodeVal; + + return pos; + }; + + // Collapsed range + if (rng.collapsed) { + pos = getCharPos(sc, so); + + ieRng = body.createTextRange(); + ieRng.move('character', pos); + ieRng.select(); + + return; + } else { + // If same text container + if (sc == ec && sc.nodeType == 3) { + startPos = getCharPos(sc, so); + + ieRng.move('character', startPos); + ieRng.moveEnd('character', eo - so); + ieRng.select(); + + return; + } + + // Get caret positions + startPos = getCharPos(sc, so); + endPos = getCharPos(ec, eo); + ieRng = body.createTextRange(); + + // Move start of range to start character position or start element + if (startPos == -1) { + ieRng.moveToElementText(sc); + startPos = 0; + } else + ieRng.move('character', startPos); + + // Move end of range to end character position or end element + tmpRng = body.createTextRange(); + + if (endPos == -1) + tmpRng.moveToElementText(ec); + else + tmpRng.move('character', endPos); + + ieRng.setEndPoint('EndToEnd', tmpRng); + ieRng.select(); + + return; + } + }; + + 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; + }; + }; + + // 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; + } + } + + }); + + // Shorten name and setup global instance + 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) { + var each = tinymce.each; + + tinymce.create('tinymce.dom.Element', { + Element : function(id, s) { + var t = this, dom, el; + + s = s || {}; + t.id = id; + t.dom = dom = s.dom || tinymce.DOM; + t.settings = s; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = t.dom.get(t.id); + + each([ + 'getPos', + 'getRect', + 'getParent', + 'add', + 'setStyle', + 'getStyle', + 'setStyles', + 'setAttrib', + 'setAttribs', + 'getAttrib', + 'addClass', + 'removeClass', + 'hasClass', + 'getOuterHTML', + 'setOuterHTML', + 'remove', + 'show', + 'hide', + 'isHidden', + 'setHTML', + 'get' + ], 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; + }; + }); + }, + + on : function(n, f, s) { + return tinymce.dom.Event.add(this.id, n, f, s); + }, + + getXY : function() { + return { + x : parseInt(this.getStyle('left')), + y : parseInt(this.getStyle('top')) + }; + }, + + getSize : function() { + var n = this.dom.get(this.id); + + return { + w : parseInt(this.getStyle('width') || n.clientWidth), + h : parseInt(this.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + this.setStyles({left : x, top : y}); + }, + + moveBy : function(x, y) { + var p = this.getXY(); + + this.moveTo(p.x + x, p.y + y); + }, + + resizeTo : function(w, h) { + this.setStyles({width : w, height : h}); + }, + + resizeBy : function(w, h) { + var s = this.getSize(); + + this.resizeTo(s.w + w, s.h + h); + }, + + update : function(k) { + var t = this, b, dom = t.dom; + + if (tinymce.isIE6 && t.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(t.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.setStyle(b, 'left', t.getStyle('left', 1)); + dom.setStyle(b, 'top', t.getStyle('top', 1)); + dom.setStyle(b, 'width', t.getStyle('width', 1)); + dom.setStyle(b, 'height', t.getStyle('height', 1)); + dom.setStyle(b, 'display', t.getStyle('display', 1)); + dom.setStyle(b, '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 + 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.setEndAfter(c); + t.setRng(r); + + // Delete the marker, and hopefully the caret gets placed in the right location + // Removed this since it seems to remove in FF and simply deleting it + // doesn't seem to affect the caret position in any browser + //d.execCommand('Delete', false, null); + + // 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 (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(1); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.firstChild; + + return e; + } else { + e = r.startContainer; + + if (e.nodeName == 'BODY') + return e.firstChild; + + return t.dom.getParent(e, '*'); + } + }, + + getEnd : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild; + + return e; + } else { + e = r.endContainer; + + if (e.nodeName == 'BODY') + return e.lastChild; + + return t.dom.getParent(e, '*'); + } + }, + + getBookmark : function(si) { + var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv; + sx = vp.x; + sy = vp.y; + + // Simple bookmark fast but not as persistent + if (si) + return {rng : r, scrollX : sx, scrollY : sy}; + + // Handle IE + if (isIE) { + // Control selection + if (r.item) { + e = r.item(0); + + each(t.dom.select(e.nodeName), function(n, i) { + if (e == n) { + sp = i; + return false; + } + }); + + return { + tag : e.nodeName, + index : sp, + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + tr = t.dom.doc.body.createTextRange(); + tr.moveToElementText(ro); + tr.collapse(true); + bp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(true); + sp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(false); + le = Math.abs(tr.move('character', c)) - sp; + + return { + start : sp - bp, + length : le, + scrollX : sx, + scrollY : sy + }; + } + + // Handle W3C + e = t.getNode(); + s = t.getSel(); + + if (!s) + return null; + + // Image selection + if (e && e.nodeName == 'IMG') { + return { + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + + function getPos(r, sn, en) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; + + while ((n = w.nextNode()) != null) { + if (n == sn) + d.start = p; + + if (n == en) { + d.end = p; + return d; + } + + p += trimNl(n.nodeValue || '').length; + } + + return null; + }; + + // Caret or selection + if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) { + e = getPos(ro, s.anchorNode, s.focusNode); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + // Count whitespace before + trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + + return { + start : Math.max(e.start + s.anchorOffset - wb, 0), + end : Math.max(e.end + s.focusOffset - wb, 0), + scrollX : sx, + scrollY : sy, + beg : s.anchorOffset - wb == 0 + }; + } else { + e = getPos(ro, r.startContainer, r.endContainer); + + // Count whitespace before start and end container + //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;}); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + return { + start : Math.max(e.start + r.startOffset - wb, 0), + end : Math.max(e.end + r.endOffset - wa, 0), + scrollX : sx, + scrollY : sy, + beg : r.startOffset - wb == 0 + }; + } + }, + + moveToBookmark : function(b) { + var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv; + + function getPos(r, sp, ep) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb; + + while ((n = w.nextNode()) != null) { + wa = wb = 0; + + nv = n.nodeValue || ''; + //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;}); + //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;}); + + nvl = trimNl(nv).length; + p += nvl; + + if (p >= sp && !d.startNode) { + o = sp - (p - nvl); + + // Fix for odd quirk in FF + if (b.beg && o >= nvl) + continue; + + d.startNode = n; + d.startOffset = o + wb; + } + + if (p >= ep) { + d.endNode = n; + d.endOffset = ep - (p - nvl) + wb; + return d; + } + } + + return null; + }; + + if (!b) + return false; + + t.win.scrollTo(b.scrollX, b.scrollY); + + // Handle explorer + if (isIE) { + // Handle simple + if (r = b.rng) { + try { + r.select(); + } catch (ex) { + // Ignore + } + + return true; + } + + t.win.focus(); + + // Handle control bookmark + if (b.tag) { + r = ro.createControlRange(); + + each(t.dom.select(b.tag), function(n, i) { + if (i == b.index) + r.addElement(n); + }); + } else { + // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs + try { + // Incorrect bookmark + if (b.start < 0) + return true; + + r = s.createRange(); + r.moveToElementText(ro); + r.collapse(true); + r.moveStart('character', b.start); + r.moveEnd('character', b.length); + } catch (ex2) { + return true; + } + } + + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + + return true; + } + + // Handle W3C + if (!s) + return false; + + // Handle simple + if (b.rng) { + s.removeAllRanges(); + s.addRange(b.rng); + } else { + if (is(b.start) && is(b.end)) { + try { + sd = getPos(ro, b.start, b.end); + + if (sd) { + r = t.dom.doc.createRange(); + r.setStart(sd.startNode, sd.startOffset); + r.setEnd(sd.endNode, sd.endOffset); + s.removeAllRanges(); + s.addRange(r); + } + + if (!tinymce.isOpera) + t.win.focus(); + } catch (ex) { + // Ignore + } + } + } + }, + + select : function(n, c) { + var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document; + + function find(n, start) { + var walker, o; + + if (n) { + walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + // Find first/last non empty text node + while (n = walker.nextNode()) { + o = n; + + if (tinymce.trim(n.nodeValue).length != 0) { + if (start) + return n; + else + o = n; + } + } + } + + return o; + }; + + if (isIE) { + try { + b = d.body; + + if (/^(IMG|TABLE)$/.test(n.nodeName)) { + r = b.createControlRange(); + r.addElement(n); + } else { + r = b.createTextRange(); + r.moveToElementText(n); + } + + r.select(); + } catch (ex) { + // Throws illigal agrument in IE some times + } + } else { + if (c) { + fn = find(n, 1) || t.dom.select('br:first', n)[0]; + ln = find(n, 0) || t.dom.select('br:last', n)[0]; + + if (fn && ln) { + r = d.createRange(); + + if (fn.nodeName == 'BR') + r.setStartBefore(fn); + else + r.setStart(fn, 0); + + if (ln.nodeName == 'BR') + r.setEndBefore(ln); + else + r.setEnd(ln, ln.nodeValue.length); + } else + r.selectNode(n); + } else + r.selectNode(n); + + t.setRng(r); + } + + return n; + }, + + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + return !s || r.boundingWidth == 0 || 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 = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange(); + + 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, r = t.getRng(), s = t.getSel(), e; + + if (!isIE) { + // Range maybe lost after the editor is made visible again + if (!r) + return t.dom.getRoot(); + + e = r.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!r.collapsed) { + // If the anchor node is a element instead of a text node then return this element + if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1) + return s.anchorNode.childNodes[s.anchorOffset]; + + if (r.startContainer == r.endContainer) { + if (r.startOffset - r.endOffset < 2) { + if (r.startContainer.hasChildNodes()) + e = r.startContainer.childNodes[r.startOffset]; + } + } + } + + return t.dom.getParent(e, '*'); + } + + return r.item ? r.item(0) : r.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('' + n + '>'); + + if (this.settings.indentation > 0) + this.writeRaw('\n'); + } + }, + + writeFullEndElement : function() { + if (this.tags.length > 0) { + this._writeAttributesEnd(); + this.writeRaw('' + this.tags.pop() + '>'); + + 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', + bool_attrs : /(checked|disabled|readonly|selected|nowrap)/, + valid_elements : '*[*]', + extended_valid_elements : 0, + valid_child_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, + font_size_style_values : 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; + + 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 '' + c + '>'; + + 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 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 ]*>)(.*?)(<\/script>)/g}, + {pattern : /(]*>)(.*?)(<\/noscript>)/g}, + {pattern : /('; + }); + + // Wrap noscript elements + h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { + return ''; + }); + } + + h = h.replace(//g, ''); + + // Process all tags with src, href or style + h = h.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi, function(a, n) { + function handle(m, b, c) { + var u = c; + + // Tag already got a mce_ version + if (a.indexOf('mce_' + b) != -1) + return m; + + if (b == 'style') { + // No mce_style for elements with these since they might get resized by the user + if (t._isRes(c)) + return m; + + if (s.hex_colors) { + u = u.replace(/rgb\([^\)]+\)/g, function(v) { + return t.toHex(v); + }); + } + + if (s.url_converter) { + u = u.replace(/url\([\'\"]?([^\)\'\"]+)\)/g, function(x, c) { + return 'url(' + t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)) + ')'; + }); + } + } else if (b != 'coords' && b != 'shape') { + if (s.url_converter) + u = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)); + } + + return ' ' + b + '="' + c + '" mce_' + b + '="' + u + '"'; + }; + + a = a.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi, handle); // W3C + a = a.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi, handle); // W3C + + return a.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi, handle); // IE + }); + } + + 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; + + return this.run(e, function(e) { + var n, tp; + + e = t.get(e); + d = d || e.ownerDocument || t.doc; + + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else { + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + } + }); + }, + + decode : function(s) { + var e, n, v; + + // Look for entities to decode + if (/&[^;]+;/.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.nextSibling); + } + + return v || s; + } + + return s; + }, + + encode : function(s) { + return s ? ('' + s).replace(/[<>&\"]/g, function (c, b) { + switch (c) { + case '&': + return '&'; + + case '"': + return '"'; + + case '<': + return '<'; + + case '>': + return '>'; + } + + return c; + }) : s; + }, + + insertAfter : function(n, r) { + var t = this; + + r = t.get(r); + + return this.run(n, function(n) { + var p, ns; + + p = r.parentNode; + ns = r.nextSibling; + + if (ns) + p.insertBefore(n, ns); + else + p.appendChild(n); + + return n; + }); + }, + + isBlock : function(n) { + if (n.nodeType && n.nodeType !== 1) + return false; + + n = n.nodeName || n; + + return /^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.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(o.childNodes, function(c) { + n.appendChild(c.cloneNode(true)); + }); + } + + // Fix IE psuedo leak for elements since replacing elements if fairly common + // Will break parentNode for some unknown reason + if (t.fixPsuedoLeaks && o.nodeType === 1) { + o.parentNode.insertBefore(n, o); + t.remove(o); + return n; + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + 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; + + // 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(/([a-z0-9\:\-_]+)=/gi, function(a, b) { + o.push({specified : 1, nodeName : b}); + }); + + 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); + }, + + 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 sence + // but we don't want that in our code since it serves no purpose + // For example if this is chopped: + // text 1CHOPtext 2 + // would produce: + // text 1CHOPtext 2 + // this function will then trim of empty edges and produce: + // text 1CHOPtext 2 + function trimEdge(n, na) { + n = n[na]; + + if (n && n[na] && n[na].nodeType == 1 && isEmpty(n[na])) + t.remove(n[na]); + }; + + function isEmpty(n) { + n = t.getOuterHTML(n); + n = n.replace(/<(img|hr|table)/gi, '-'); // Keep these convert them to - chars + n = n.replace(/<[^>]+>/g, ''); // Remove all tags + + return n.replace(/[ \t\r\n]+| | /g, '') == ''; + }; + + if (pe && e) { + // Get before chunk + r.setStartBefore(pe); + r.setEndBefore(e); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStartAfter(e); + r.setEndAfter(pe); + aft = r.extractContents(); + + // Insert chunks and remove parent + pa = pe.parentNode; + + // Remove right side edge of the before contents + trimEdge(bef, 'lastChild'); + + if (!isEmpty(bef)) + pa.insertBefore(bef, pe); + + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Remove left site edge of the after contents + trimEdge(aft, 'firstChild'); + + if (!isEmpty(aft)) + pa.insertBefore(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); + }, + + + _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; + } + */ + + }); + + // Setup page DOM + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); +(function(ns) { + // Traverse constants + var EXTRACT = 0, CLONE = 1, DELETE = 2, extend = tinymce.extend; + + function indexOf(child, parent) { + var i, node; + + if (child.parentNode != parent) + return -1; + + for (node = parent.firstChild, i = 0; node != child; node = node.nextSibling) + i++; + + return i; + }; + + function nodeIndex(n) { + var i = 0; + + while (n.previousSibling) { + i++; + n = n.previousSibling; + } + + return i; + }; + + function getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child != null && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child != null) + return child; + + return container; + }; + + // Range constructor + function Range(dom) { + var d = dom.doc; + + extend(this, { + dom : dom, + + // Inital states + startContainer : d, + startOffset : 0, + endContainer : d, + endOffset : 0, + collapsed : true, + commonAncestorContainer : d, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3 + }); + }; + + // Add range methods + extend(Range.prototype, { + setStart : function(n, o) { + this._setEndPoint(true, n, o); + }, + + setEnd : function(n, o) { + this._setEndPoint(false, n, o); + }, + + setStartBefore : function(n) { + this.setStart(n.parentNode, nodeIndex(n)); + }, + + setStartAfter : function(n) { + this.setStart(n.parentNode, nodeIndex(n) + 1); + }, + + setEndBefore : function(n) { + this.setEnd(n.parentNode, nodeIndex(n)); + }, + + setEndAfter : function(n) { + this.setEnd(n.parentNode, nodeIndex(n) + 1); + }, + + collapse : function(ts) { + var t = this; + + if (ts) { + t.endContainer = t.startContainer; + t.endOffset = t.startOffset; + } else { + t.startContainer = t.endContainer; + t.startOffset = t.endOffset; + } + + t.collapsed = true; + }, + + selectNode : function(n) { + this.setStartBefore(n); + this.setEndAfter(n); + }, + + selectNodeContents : function(n) { + this.setStart(n, 0); + this.setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }, + + compareBoundaryPoints : function(h, r) { + var t = this, sc = t.startContainer, so = t.startOffset, ec = t.endContainer, eo = t.endOffset; + + // Check START_TO_START + if (h === 0) + return t._compareBoundaryPoints(sc, so, sc, so); + + // Check START_TO_END + if (h === 1) + return t._compareBoundaryPoints(sc, so, ec, eo); + + // Check END_TO_END + if (h === 2) + return t._compareBoundaryPoints(ec, eo, ec, eo); + + // Check END_TO_START + if (h === 3) + return t._compareBoundaryPoints(ec, eo, sc, so); + }, + + deleteContents : function() { + this._traverse(DELETE); + }, + + extractContents : function() { + return this._traverse(EXTRACT); + }, + + cloneContents : function() { + return this._traverse(CLONE); + }, + + insertNode : function(n) { + var t = this, nn, o; + + // Node is TEXT_NODE or CDATA + if (n.nodeType === 3 || n.nodeType === 4) { + nn = t.startContainer.splitText(t.startOffset); + t.startContainer.parentNode.insertBefore(n, nn); + } else { + // Insert element node + if (t.startContainer.childNodes.length > 0) + o = t.startContainer.childNodes[t.startOffset]; + + t.startContainer.insertBefore(n, o); + } + }, + + surroundContents : function(n) { + var t = this, f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }, + + cloneRange : function() { + var t = this; + + return extend(new Range(t.dom), { + startContainer : t.startContainer, + startOffset : t.startOffset, + endContainer : t.endContainer, + endOffset : t.endOffset, + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }, + +/* + detach : function() { + // Not implemented + }, +*/ + // Internal methods + + _isCollapsed : function() { + return (this.startContainer == this.endContainer && this.startOffset == this.endOffset); + }, + + _compareBoundaryPoints : function (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 + } else if (offsetA < offsetB) { + return -1; // before + } else { + 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 + } else { + 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 + } else { + 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 = this.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; + } + }, + + _setEndPoint : function(st, n, o) { + var t = this, ec, sc; + + if (st) { + t.startContainer = n; + t.startOffset = o; + } else { + t.endContainer = n; + t.endOffset = 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.endContainer; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t.startContainer; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc != ec) { + t.collapse(st); + } else { + // 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 (t._compareBoundaryPoints(t.startContainer, t.startOffset, t.endContainer, t.endOffset) > 0) + t.collapse(st); + } + + t.collapsed = t._isCollapsed(); + t.commonAncestorContainer = t.dom.findCommonAncestor(t.startContainer, t.endContainer); + }, + + // This code is heavily "inspired" by the Apache Xerces implementation. I hope they don't mind. :) + + _traverse : function(how) { + var t = this, c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t.startContainer == t.endContainer) + return t._traverseSameContainer(how); + + for (c = t.endContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.startContainer) + return t._traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t.startContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.endContainer) + return t._traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t.startContainer; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t.endContainer; + 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 t._traverseCommonAncestors(startNode, endNode, how); + }, + + _traverseSameContainer : function(how) { + var t = this, frag, s, sub, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t.startOffset == t.endOffset) + return frag; + + // Text node needs special case handling + if (t.startContainer.nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t.startContainer.nodeValue; + sub = s.substring(t.startOffset, t.endOffset); + + // set the original text node to its new value + if (how != CLONE) { + t.startContainer.deleteData(t.startOffset, t.endOffset - t.startOffset); + + // Nothing is partially selected, so collapse to start point + t.collapse(true); + } + + if (how == DELETE) + return null; + + frag.appendChild(t.dom.doc.createTextNode(sub)); + return frag; + } + + // Copy nodes between the start/end offsets. + n = getSelectedNode(t.startContainer, t.startOffset); + cnt = t.endOffset - t.startOffset; + + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._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; + }, + + _traverseCommonStartContainer : function(endAncestor, how) { + var t = this, frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = indexOf(endAncestor, t.startContainer); + cnt = endIdx - t.startOffset; + + 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 = t._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; + }, + + _traverseCommonEndContainer : function(startAncestor, how) { + var t = this, frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = indexOf(startAncestor, t.endContainer); + ++startIdx; // Because we already traversed it.... + + cnt = t.endOffset - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseCommonAncestors : function(startAncestor, endAncestor, how) { + var t = this, n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = indexOf(startAncestor, commonParent); + endOffset = indexOf(endAncestor, commonParent); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = t._traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseRightBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.endContainer, t.endOffset - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent; + var isFullySelected = next != t.endContainer; + + if (next == root) + return t._traverseNode(next, isFullySelected, false, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, false, how); + + while (parent != null) { + while (next != null) { + prevSibling = next.previousSibling; + clonedChild = t._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 = t._traverseNode(parent, false, false, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseLeftBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.startContainer, t.startOffset); + var isFullySelected = next != t.startContainer, parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return t._traverseNode(next, isFullySelected, true, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, true, how); + + while (parent != null) { + while (next != null) { + nextSibling = next.nextSibling; + clonedChild = t._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 = t._traverseNode(parent, false, true, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseNode : function(n, isFullySelected, isLeft, how) { + var t = this, txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return t._traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t.startOffset; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t.endOffset; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return null; + + newNode = n.cloneNode(false); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return null; + + return n.cloneNode(false); + }, + + _traverseFullySelected : function(n, how) { + var t = this; + + if (how != DELETE) + return how == CLONE ? n.cloneNode(true) : n; + + n.parentNode.removeChild(n); + return null; + } + }); + + ns.Range = Range; +})(tinymce.dom); +(function() { + function Selection(selection) { + var t = this, invisibleChar = '\uFEFF', range, lastIERng; + + 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 1; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return 1; + } + + return 0; + }; + + function getRange() { + var dom = selection.dom, ieRange = selection.getRng(), domRange = dom.createRng(), startPos, endPos, element, sc, ec, collapsed; + + function findIndex(element) { + var nl = element.parentNode.childNodes, i; + + for (i = nl.length - 1; i >= 0; i--) { + if (nl[i] == element) + return i; + } + + return -1; + }; + + function findEndPoint(start) { + var rng = ieRange.duplicate(), parent, i, nl, n, offset = 0, index = 0, pos, tmpRng; + + // Insert marker character + rng.collapse(start); + parent = rng.parentElement(); + rng.pasteHTML(invisibleChar); // Needs to be a pasteHTML instead of .text = since IE has a bug with nodeValue + + // Find marker character + nl = parent.childNodes; + for (i = 0; i < nl.length; i++) { + n = nl[i]; + + // Calculate node index excluding text node fragmentation + if (i > 0 && (n.nodeType !== 3 || nl[i - 1].nodeType !== 3)) + index++; + + // If text node then calculate offset + if (n.nodeType === 3) { + // Look for marker + pos = n.nodeValue.indexOf(invisibleChar); + if (pos !== -1) { + offset += pos; + break; + } + + offset += n.nodeValue.length; + } else + offset = 0; + } + + // Remove marker character + rng.moveStart('character', -1); + rng.text = ''; + + return {index : index, offset : offset, parent : parent}; + }; + + // 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, findIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + // Check collapsed state + collapsed = selection.isCollapsed(); + + // Find start and end pos index and offset + startPos = findEndPoint(true); + endPos = findEndPoint(false); + + // Normalize the elements to avoid fragmented dom + startPos.parent.normalize(); + endPos.parent.normalize(); + + // Set start container and offset + sc = startPos.parent.childNodes[Math.min(startPos.index, startPos.parent.childNodes.length - 1)]; + + if (sc.nodeType != 3) + domRange.setStart(startPos.parent, startPos.index); + else + domRange.setStart(startPos.parent.childNodes[startPos.index], startPos.offset); + + // Set end container and offset + ec = endPos.parent.childNodes[Math.min(endPos.index, endPos.parent.childNodes.length - 1)]; + + if (ec.nodeType != 3) { + if (!collapsed) + endPos.index++; + + domRange.setEnd(endPos.parent, endPos.index); + } else + domRange.setEnd(endPos.parent.childNodes[endPos.index], endPos.offset); + + // If not collapsed then make sure offsets are valid + if (!collapsed) { + sc = domRange.startContainer; + if (sc.nodeType == 1) + domRange.setStart(sc, Math.min(domRange.startOffset, sc.childNodes.length)); + + ec = domRange.endContainer; + if (ec.nodeType == 1) + domRange.setEnd(ec, Math.min(domRange.endOffset, ec.childNodes.length)); + } + + // Restore selection to new range + t.addRange(domRange); + + return domRange; + }; + + this.addRange = function(rng) { + var ieRng, body = selection.dom.doc.body, startPos, endPos, sc, so, ec, eo; + + // Setup some shorter versions + sc = rng.startContainer; + so = rng.startOffset; + ec = rng.endContainer; + eo = rng.endOffset; + ieRng = body.createTextRange(); + + // Find element + sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc; + ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec; + + // 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(); + + return; + } + + function getCharPos(container, offset) { + var nodeVal, rng, pos; + + if (container.nodeType != 3) + return -1; + + nodeVal = container.nodeValue; + rng = body.createTextRange(); + + // Insert marker at offset position + container.nodeValue = nodeVal.substring(0, offset) + invisibleChar + nodeVal.substring(offset); + + // Find char pos of marker and remove it + rng.moveToElementText(container.parentNode); + rng.findText(invisibleChar); + pos = Math.abs(rng.moveStart('character', -0xFFFFF)); + container.nodeValue = nodeVal; + + return pos; + }; + + // Collapsed range + if (rng.collapsed) { + pos = getCharPos(sc, so); + + ieRng = body.createTextRange(); + ieRng.move('character', pos); + ieRng.select(); + + return; + } else { + // If same text container + if (sc == ec && sc.nodeType == 3) { + startPos = getCharPos(sc, so); + + ieRng.move('character', startPos); + ieRng.moveEnd('character', eo - so); + ieRng.select(); + + return; + } + + // Get caret positions + startPos = getCharPos(sc, so); + endPos = getCharPos(ec, eo); + ieRng = body.createTextRange(); + + // Move start of range to start character position or start element + if (startPos == -1) { + ieRng.moveToElementText(sc); + startPos = 0; + } else + ieRng.move('character', startPos); + + // Move end of range to end character position or end element + tmpRng = body.createTextRange(); + + if (endPos == -1) + tmpRng.moveToElementText(ec); + else + tmpRng.move('character', endPos); + + ieRng.setEndPoint('EndToEnd', tmpRng); + ieRng.select(); + + return; + } + }; + + 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; + }; + }; + + // 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; + } + } + + }); + + // Shorten name and setup global instance + 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) { + var each = tinymce.each; + + tinymce.create('tinymce.dom.Element', { + Element : function(id, s) { + var t = this, dom, el; + + s = s || {}; + t.id = id; + t.dom = dom = s.dom || tinymce.DOM; + t.settings = s; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = t.dom.get(t.id); + + each([ + 'getPos', + 'getRect', + 'getParent', + 'add', + 'setStyle', + 'getStyle', + 'setStyles', + 'setAttrib', + 'setAttribs', + 'getAttrib', + 'addClass', + 'removeClass', + 'hasClass', + 'getOuterHTML', + 'setOuterHTML', + 'remove', + 'show', + 'hide', + 'isHidden', + 'setHTML', + 'get' + ], 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; + }; + }); + }, + + on : function(n, f, s) { + return tinymce.dom.Event.add(this.id, n, f, s); + }, + + getXY : function() { + return { + x : parseInt(this.getStyle('left')), + y : parseInt(this.getStyle('top')) + }; + }, + + getSize : function() { + var n = this.dom.get(this.id); + + return { + w : parseInt(this.getStyle('width') || n.clientWidth), + h : parseInt(this.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + this.setStyles({left : x, top : y}); + }, + + moveBy : function(x, y) { + var p = this.getXY(); + + this.moveTo(p.x + x, p.y + y); + }, + + resizeTo : function(w, h) { + this.setStyles({width : w, height : h}); + }, + + resizeBy : function(w, h) { + var s = this.getSize(); + + this.resizeTo(s.w + w, s.h + h); + }, + + update : function(k) { + var t = this, b, dom = t.dom; + + if (tinymce.isIE6 && t.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(t.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.setStyle(b, 'left', t.getStyle('left', 1)); + dom.setStyle(b, 'top', t.getStyle('top', 1)); + dom.setStyle(b, 'width', t.getStyle('width', 1)); + dom.setStyle(b, 'height', t.getStyle('height', 1)); + dom.setStyle(b, 'display', t.getStyle('display', 1)); + dom.setStyle(b, '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 + 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.setEndAfter(c); + t.setRng(r); + + // Delete the marker, and hopefully the caret gets placed in the right location + // Removed this since it seems to remove in FF and simply deleting it + // doesn't seem to affect the caret position in any browser + //d.execCommand('Delete', false, null); + + // 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 (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(1); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.firstChild; + + return e; + } else { + e = r.startContainer; + + if (e.nodeName == 'BODY') + return e.firstChild; + + return t.dom.getParent(e, '*'); + } + }, + + getEnd : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild; + + return e; + } else { + e = r.endContainer; + + if (e.nodeName == 'BODY') + return e.lastChild; + + return t.dom.getParent(e, '*'); + } + }, + + getBookmark : function(si) { + var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv; + sx = vp.x; + sy = vp.y; + + // Simple bookmark fast but not as persistent + if (si) + return {rng : r, scrollX : sx, scrollY : sy}; + + // Handle IE + if (isIE) { + // Control selection + if (r.item) { + e = r.item(0); + + each(t.dom.select(e.nodeName), function(n, i) { + if (e == n) { + sp = i; + return false; + } + }); + + return { + tag : e.nodeName, + index : sp, + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + tr = t.dom.doc.body.createTextRange(); + tr.moveToElementText(ro); + tr.collapse(true); + bp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(true); + sp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(false); + le = Math.abs(tr.move('character', c)) - sp; + + return { + start : sp - bp, + length : le, + scrollX : sx, + scrollY : sy + }; + } + + // Handle W3C + e = t.getNode(); + s = t.getSel(); + + if (!s) + return null; + + // Image selection + if (e && e.nodeName == 'IMG') { + return { + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + + function getPos(r, sn, en) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; + + while ((n = w.nextNode()) != null) { + if (n == sn) + d.start = p; + + if (n == en) { + d.end = p; + return d; + } + + p += trimNl(n.nodeValue || '').length; + } + + return null; + }; + + // Caret or selection + if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) { + e = getPos(ro, s.anchorNode, s.focusNode); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + // Count whitespace before + trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + + return { + start : Math.max(e.start + s.anchorOffset - wb, 0), + end : Math.max(e.end + s.focusOffset - wb, 0), + scrollX : sx, + scrollY : sy, + beg : s.anchorOffset - wb == 0 + }; + } else { + e = getPos(ro, r.startContainer, r.endContainer); + + // Count whitespace before start and end container + //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;}); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + return { + start : Math.max(e.start + r.startOffset - wb, 0), + end : Math.max(e.end + r.endOffset - wa, 0), + scrollX : sx, + scrollY : sy, + beg : r.startOffset - wb == 0 + }; + } + }, + + moveToBookmark : function(b) { + var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv; + + function getPos(r, sp, ep) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb; + + while ((n = w.nextNode()) != null) { + wa = wb = 0; + + nv = n.nodeValue || ''; + //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;}); + //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;}); + + nvl = trimNl(nv).length; + p += nvl; + + if (p >= sp && !d.startNode) { + o = sp - (p - nvl); + + // Fix for odd quirk in FF + if (b.beg && o >= nvl) + continue; + + d.startNode = n; + d.startOffset = o + wb; + } + + if (p >= ep) { + d.endNode = n; + d.endOffset = ep - (p - nvl) + wb; + return d; + } + } + + return null; + }; + + if (!b) + return false; + + t.win.scrollTo(b.scrollX, b.scrollY); + + // Handle explorer + if (isIE) { + // Handle simple + if (r = b.rng) { + try { + r.select(); + } catch (ex) { + // Ignore + } + + return true; + } + + t.win.focus(); + + // Handle control bookmark + if (b.tag) { + r = ro.createControlRange(); + + each(t.dom.select(b.tag), function(n, i) { + if (i == b.index) + r.addElement(n); + }); + } else { + // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs + try { + // Incorrect bookmark + if (b.start < 0) + return true; + + r = s.createRange(); + r.moveToElementText(ro); + r.collapse(true); + r.moveStart('character', b.start); + r.moveEnd('character', b.length); + } catch (ex2) { + return true; + } + } + + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + + return true; + } + + // Handle W3C + if (!s) + return false; + + // Handle simple + if (b.rng) { + s.removeAllRanges(); + s.addRange(b.rng); + } else { + if (is(b.start) && is(b.end)) { + try { + sd = getPos(ro, b.start, b.end); + + if (sd) { + r = t.dom.doc.createRange(); + r.setStart(sd.startNode, sd.startOffset); + r.setEnd(sd.endNode, sd.endOffset); + s.removeAllRanges(); + s.addRange(r); + } + + if (!tinymce.isOpera) + t.win.focus(); + } catch (ex) { + // Ignore + } + } + } + }, + + select : function(n, c) { + var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document; + + function find(n, start) { + var walker, o; + + if (n) { + walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + // Find first/last non empty text node + while (n = walker.nextNode()) { + o = n; + + if (tinymce.trim(n.nodeValue).length != 0) { + if (start) + return n; + else + o = n; + } + } + } + + return o; + }; + + if (isIE) { + try { + b = d.body; + + if (/^(IMG|TABLE)$/.test(n.nodeName)) { + r = b.createControlRange(); + r.addElement(n); + } else { + r = b.createTextRange(); + r.moveToElementText(n); + } + + r.select(); + } catch (ex) { + // Throws illigal agrument in IE some times + } + } else { + if (c) { + fn = find(n, 1) || t.dom.select('br:first', n)[0]; + ln = find(n, 0) || t.dom.select('br:last', n)[0]; + + if (fn && ln) { + r = d.createRange(); + + if (fn.nodeName == 'BR') + r.setStartBefore(fn); + else + r.setStart(fn, 0); + + if (ln.nodeName == 'BR') + r.setEndBefore(ln); + else + r.setEnd(ln, ln.nodeValue.length); + } else + r.selectNode(n); + } else + r.selectNode(n); + + t.setRng(r); + } + + return n; + }, + + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + return !s || r.boundingWidth == 0 || 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 = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange(); + + 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, r = t.getRng(), s = t.getSel(), e; + + if (!isIE) { + // Range maybe lost after the editor is made visible again + if (!r) + return t.dom.getRoot(); + + e = r.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!r.collapsed) { + // If the anchor node is a element instead of a text node then return this element + if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1) + return s.anchorNode.childNodes[s.anchorOffset]; + + if (r.startContainer == r.endContainer) { + if (r.startOffset - r.endOffset < 2) { + if (r.startContainer.hasChildNodes()) + e = r.startContainer.childNodes[r.startOffset]; + } + } + } + + return t.dom.getParent(e, '*'); + } + + return r.item ? r.item(0) : r.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('' + n + '>'); + + if (this.settings.indentation > 0) + this.writeRaw('\n'); + } + }, + + writeFullEndElement : function() { + if (this.tags.length > 0) { + this._writeAttributesEnd(); + this.writeRaw('' + this.tags.pop() + '>'); + + 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', + bool_attrs : /(checked|disabled|readonly|selected|nowrap)/, + valid_elements : '*[*]', + extended_valid_elements : 0, + valid_child_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, + font_size_style_values : 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; + + 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 '' + c + '>'; + + 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 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 ]*>)(.*?)(<\/script>)/g}, + {pattern : /(]*>)(.*?)(<\/noscript>)/g}, + {pattern : /(
Signing up with Checkout by Amazon
To configure Checkout by Amazon™ you will need to enter your Checkout by Amazon™ Merchant ID, Access Key ID, and Secret Access Key.
If you do not already have a Checkout by Amazon™ account, click here to create one now. Sign-Up
To locate your Merchant ID, sign in to your Seller Central Checkout by Amazon™ account and click Settings > Checkout Pipeline Settings.
To locate your Access Key ID and Secret Access Key, sign in to your Seller Central Checkout by Amazon™ account and click Integration > AWS Key. Click the link to read the Amazon Web Services Customer Agreement, and then click the check box, if you are setting up a new Access Key ID.
To enable XML Order Reports click Settings > Checkout Pipeline Settings, and then clicking Edit under the Order Report Settings section. Select Order Report Type as XML to get XML Order Reports using SOAP APIs. Configure your downloads for hourly.
For additional information on setting up your Checkout by Amazon account, Click Here - FAQ.
Signup for Checkout by Amazon
sku
url_key
status
visibility
- + + - - + +
+ - -
Signing up with Ogone
Please enter the correct post back url and offline processiong url in Ogone configuration
post back url example: http://myMagentoStore.com/ogone/api/postBack
offline processing url example: http://myMagentoStore.com/ogone/api/offlineProcess
'; - //var_dump( (array)$res); usort($res, array($this, '_sortMethods')); - //var_dump((array)$res); - // echo '
"; -//print_r($data); + $rssObj->_addHeader($data); $_collection = $category->getCollection(); @@ -81,18 +80,6 @@ protected function _toHtml() $layer->prepareProductCollection($productCollection); $productCollection->addCountToCategories($_collection); - /*if ($_collection->count()) { - foreach ($_collection as $_category){ - $data = array( - 'title' => $_category->getName(), - 'link' => $_category->getCategoryUrl(), - 'description' => $this->helper('rss')->__('Total Products: %s', $_category->getProductCount()), - ); - - $rssObj->_addEntry($data); - } - } - */ $category->getProductCollection()->setStoreId($storeId); /* only load latest 50 products @@ -100,35 +87,61 @@ protected function _toHtml() $_productCollection = $currentyCateogry ->getProductCollection() ->addAttributeToSort('updated_at','desc') + ->setVisibility(Mage::getSingleton('catalog/product_visibility')->getVisibleInCatalogIds()) ->setCurPage(1) ->setPageSize(50) ; -//echo "".$_productCollection->getSelect(); + if ($_productCollection->getSize()>0) { + $args = array('rssObj' => $rssObj); foreach ($_productCollection as $_product) { - $final_price = $_product->getFinalPrice(); - $description = '
Price:'.Mage::helper('core')->currency($_product->getPrice()). - ($_product->getPrice() != $final_price ? ' Special Price:'. Mage::helper('core')->currency($final_price) : ''). - '
Price:'.Mage::helper('core')->currency($product->getPrice()); + if ($product->getPrice() != $product->getFinalPrice()) { + $description .= ' Special Price:' . Mage::helper('core')->currency($product->getFinalPrice()); + } + $description .= '
Price:'.Mage::helper('core')->currency($product->getPrice()). - ($product->getPrice() != $final_price ? ' Special Price:'. Mage::helper('core')->currency($final_price) : ''). - '
Price:' . Mage::helper('core')->currency($product->getPrice()); + if ($product->getPrice() != $product->getFinalPrice()){ + $description .= ' Special Price:' . Mage::helper('core')->currency($product->getFinalPrice()); + } + $description .= '
Price:'.Mage::helper('core')->currency($product->getPrice()). - ' Special Price:'. Mage::helper('core')->currency($special_price). - ($result['use_special'] && $result['special_to_date'] ? ' Special Expires on: '.$this->formatDate($result['special_to_date'], Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM) : ''). - '
Price:'.Mage::helper('core')->currency($product->getPrice()). + ' Special Price:'. Mage::helper('core')->currency($specialPrice). + ($result['use_special'] && $result['special_to_date'] ? ' Special Expires on: '.$this->formatDate($result['special_to_date'], Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM) : ''). + '
"; -//print_r($args['row']); $row = $args['row']; - $special_price = $row['special_price']; - $rule_price = $row['rule_price']; - if (!$rule_price || ($rule_price && $special_price && $special_price<=$rule_price)) { - $row['start_date'] = $row['special_from_date']; - $row['use_special'] = true; + + if ($product->getAllowedPriceInRss()) { + $specialPrice = $row['special_price']; + $rule_price = $row['rule_price']; + if (!$rulePrice || ($rulePrice && $specialPrice && $specialPrice<=$rulePrice)) { + $row['start_date'] = $row['special_from_date']; + $row['use_special'] = true; + } else { + $row['start_date'] = $row['rule_start_date']; + $row['use_special'] = false; + } + $row['allowed_price_in_rss'] = true; } else { - $row['start_date'] = $row['rule_start_date']; - $row['use_special'] = false; + $row['allowed_price_in_rss'] = false; } $args['results'][] = $row; } diff --git a/app/code/core/Mage/Rss/Block/Catalog/Tag.php b/app/code/core/Mage/Rss/Block/Catalog/Tag.php index 1bf0b34726..be79fdcaad 100644 --- a/app/code/core/Mage/Rss/Block/Catalog/Tag.php +++ b/app/code/core/Mage/Rss/Block/Catalog/Tag.php @@ -64,6 +64,8 @@ protected function _toHtml() ->addTagFilter($tagModel->getId()) ->addStoreFilter($storeId); + $_collection->setVisibility(Mage::getSingleton('catalog/product_visibility')->getVisibleInCatalogIds()); + $product = Mage::getModel('catalog/product'); Mage::getSingleton('core/resource_iterator') @@ -72,6 +74,11 @@ protected function _toHtml() return $rssObj->createRssXml(); } + /** + * Preparing data and adding to rss object + * + * @param array $args + */ public function addTaggedItemXml($args) { $product = $args['product']; @@ -84,20 +91,25 @@ public function addTaggedItemXml($args) return; } + $allowedPriceInRss = $product->getAllowedPriceInRss(); + $product->unsetData()->load($args['row']['entity_id']); $description = '
Price:'.Mage::helper('core')->currency($product->getFinalPrice()).'
+ Shopping Cart Price Rules + adminhtml/promo_quote/ + promo/quote + Mage_Sales +
+ Shopping Cart Price Rules +
- Shopping Cart Price Rules - adminhtml/promo_quote/ - promo/quote - Mage_Sales -
- Shopping Cart Price Rules -
__('Show Report for') ?>: @@ -67,4 +72,4 @@ setLocation('getSwitchUrl() ?>'+storeParam); } - \ No newline at end of file + diff --git a/app/design/adminhtml/default/default/template/sales/order/create/coupons/form.phtml b/app/design/adminhtml/default/default/template/sales/order/create/coupons/form.phtml index 726317de6a..1236382b13 100644 --- a/app/design/adminhtml/default/default/template/sales/order/create/coupons/form.phtml +++ b/app/design/adminhtml/default/default/template/sales/order/create/coupons/form.phtml @@ -24,6 +24,12 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> + __('Apply Coupon Code') ?> diff --git a/app/design/adminhtml/default/default/template/sales/order/view/info.phtml b/app/design/adminhtml/default/default/template/sales/order/view/info.phtml index 41fb97a639..95d66a66d0 100644 --- a/app/design/adminhtml/default/default/template/sales/order/view/info.phtml +++ b/app/design/adminhtml/default/default/template/sales/order/view/info.phtml @@ -84,7 +84,7 @@ $orderStoreDate = $this->formatDate($_order->getCreatedAtStoreDate(), 'medium', getRemoteIp()): ?> __('Placed from IP') ?> - getRemoteIp() ?> + getRemoteIp(); echo ($_order->getXForwardedFor())?' (' . $_order->getXForwardedFor() . ')':''; ?> getGlobalCurrencyCode() != $_order->getBaseCurrencyCode()): ?> diff --git a/app/design/adminhtml/default/default/template/system/cache/edit.phtml b/app/design/adminhtml/default/default/template/system/cache/edit.phtml index abae89aabc..d4c98b30a1 100644 --- a/app/design/adminhtml/default/default/template/system/cache/edit.phtml +++ b/app/design/adminhtml/default/default/template/system/cache/edit.phtml @@ -59,6 +59,8 @@ getCatalogData() as $_item): ?> + + diff --git a/app/design/adminhtml/default/default/template/system/convert/profile/wizard.phtml b/app/design/adminhtml/default/default/template/system/convert/profile/wizard.phtml index a18ed05c60..5a3a2691e3 100644 --- a/app/design/adminhtml/default/default/template/system/convert/profile/wizard.phtml +++ b/app/design/adminhtml/default/default/template/system/convert/profile/wizard.phtml @@ -143,10 +143,12 @@ function changeDirection() function updateRun(select) { - if ($(select).value=='interactive') { - $('file_list').show(); - } else { - $('file_list').hide(); + if ($('file_list') != null){ + if ($(select).value=='interactive') { + $('file_list').show(); + } else { + $('file_list').hide(); + } } } diff --git a/app/design/adminhtml/default/default/template/system/email/template/edit.phtml b/app/design/adminhtml/default/default/template/system/email/template/edit.phtml index f93b7825ba..a4aa38797c 100644 --- a/app/design/adminhtml/default/default/template/system/email/template/edit.phtml +++ b/app/design/adminhtml/default/default/template/system/email/template/edit.phtml @@ -156,6 +156,8 @@ preview: function() { if (this.typeChange) { $('preview_type').value = 1; + } else { + $('preview_type').value = 2; } if (typeof tinyMCE == 'undefined' || !tinyMCE.getInstanceById('template_text')) { $('preview_text').value = $('template_text').value; diff --git a/app/design/adminhtml/default/default/template/tag/edit/container.phtml b/app/design/adminhtml/default/default/template/tag/edit/container.phtml new file mode 100644 index 0000000000..74df22d158 --- /dev/null +++ b/app/design/adminhtml/default/default/template/tag/edit/container.phtml @@ -0,0 +1,59 @@ + +getFormInitScripts() ?> + + getHeaderHtml() ?> + getButtonsHtml('header') ?> + +isSingleStoreMode() ): ?> + getStoreSwitcherHtml() ?> + + + + getFormHtml() ?> + + getTagAssignAccordionHtml() ?> + + + + + +hasFooterButtons()): ?> + + + +getFormScripts() ?> +getAcordionsHtml() ?> \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/tag/edit/serializer.phtml b/app/design/adminhtml/default/default/template/tag/edit/serializer.phtml new file mode 100644 index 0000000000..7768569035 --- /dev/null +++ b/app/design/adminhtml/default/default/template/tag/edit/serializer.phtml @@ -0,0 +1,33 @@ + + + + + diff --git a/app/design/adminhtml/default/default/template/tag/index.phtml b/app/design/adminhtml/default/default/template/tag/index.phtml index 5b5b7750c2..19d5510d75 100644 --- a/app/design/adminhtml/default/default/template/tag/index.phtml +++ b/app/design/adminhtml/default/default/template/tag/index.phtml @@ -26,10 +26,10 @@ ?> - - getHeaderHtml(); ?> - getCreateButtonHtml(); ?> - + + getHeaderHtml(); ?> + getCreateButtonHtml(); ?> + diff --git a/app/design/adminhtml/default/default/template/widget/form/renderer/fieldset/element.phtml b/app/design/adminhtml/default/default/template/widget/form/renderer/fieldset/element.phtml index 6acc5ca48b..d211a6cc56 100644 --- a/app/design/adminhtml/default/default/template/widget/form/renderer/fieldset/element.phtml +++ b/app/design/adminhtml/default/default/template/widget/form/renderer/fieldset/element.phtml @@ -26,7 +26,8 @@ ?> getElement() ?> getNoDisplay()): ?> - +getHtmlContainerId() ?> + id=""> getType()=='hidden'): ?> getElementHtml()) ?> diff --git a/app/design/adminhtml/default/default/template/widget/grid.phtml b/app/design/adminhtml/default/default/template/widget/grid.phtml index c4f63e445c..0b6642c4c1 100644 --- a/app/design/adminhtml/default/default/template/widget/grid.phtml +++ b/app/design/adminhtml/default/default/template/widget/grid.phtml @@ -183,7 +183,7 @@ $numColumns = sizeof($this->getColumns()); getRowInitCallback()): ?> getJsObjectName() ?>.initRowCallback = getRowInitCallback() ?>; - getJsObjectName() ?>.rows.each(function(row){getRowInitCallback() ?>(getJsObjectName() ?>, row)}); + getJsObjectName() ?>.initGridRows(); getMassactionBlock()->isAvailable()): ?> getMassactionBlock()->getJavaScript() ?> diff --git a/app/design/adminhtml/default/default/template/widget/grid/massaction.phtml b/app/design/adminhtml/default/default/template/widget/grid/massaction.phtml index 99cd9aafaa..58c4b61be2 100644 --- a/app/design/adminhtml/default/default/template/widget/grid/massaction.phtml +++ b/app/design/adminhtml/default/default/template/widget/grid/massaction.phtml @@ -45,7 +45,7 @@ __('Actions') ?> - + getItems() as $_item): ?> getSelected() ? ' selected="selected"' : '')?>>getLabel() ?> @@ -77,4 +77,4 @@ getJsObjectName() ?>.setGridIds('getGridIdsJson() ?>'); - \ No newline at end of file + diff --git a/app/design/adminhtml/default/default/template/widget/grid/serializer.phtml b/app/design/adminhtml/default/default/template/widget/grid/serializer.phtml new file mode 100644 index 0000000000..39e69b60ec --- /dev/null +++ b/app/design/adminhtml/default/default/template/widget/grid/serializer.phtml @@ -0,0 +1,37 @@ + + + + + diff --git a/app/design/frontend/default/blank/layout/checkout.xml b/app/design/frontend/default/blank/layout/checkout.xml index e2075b77bb..f6be52be0a 100644 --- a/app/design/frontend/default/blank/layout/checkout.xml +++ b/app/design/frontend/default/blank/layout/checkout.xml @@ -48,6 +48,9 @@ Default layout, loads most of the pages configurablecheckout/cart_item_renderer_configurablecheckout/cart/sidebar/default.phtml + + + diff --git a/app/design/frontend/default/blank/layout/customer.xml b/app/design/frontend/default/blank/layout/customer.xml index 3d2db79f1b..8d69db5de8 100644 --- a/app/design/frontend/default/blank/layout/customer.xml +++ b/app/design/frontend/default/blank/layout/customer.xml @@ -119,6 +119,9 @@ New customer registration + + + page/1column.phtml @@ -192,7 +195,7 @@ Customer account pages, rendered for all tabs in dashboard - + @@ -239,6 +242,9 @@ Customer account address edit page + + + diff --git a/app/design/frontend/default/blank/layout/ogone.xml b/app/design/frontend/default/blank/layout/ogone.xml new file mode 100644 index 0000000000..3b4785b540 --- /dev/null +++ b/app/design/frontend/default/blank/layout/ogone.xml @@ -0,0 +1,47 @@ + + + + + + + + page/1column.phtml + + + + + + + + + + + + + + diff --git a/app/design/frontend/default/blank/template/amazonpayments/cba/success.phtml b/app/design/frontend/default/blank/template/amazonpayments/cba/success.phtml index 3aa71b7c3c..76ad44087c 100644 --- a/app/design/frontend/default/blank/template/amazonpayments/cba/success.phtml +++ b/app/design/frontend/default/blank/template/amazonpayments/cba/success.phtml @@ -31,5 +31,5 @@ SUCCESS __('Thank you for your purchase!') ?> __('You will receive an order confirmation email with details of your order and a link to track its progress.') ?> - __('Continue Shopping') ?> + __('Continue Shopping') ?> \ No newline at end of file diff --git a/app/design/frontend/default/blank/template/callouts/right_col.phtml b/app/design/frontend/default/blank/template/callouts/right_col.phtml index 6b53da831f..d5cd639eeb 100644 --- a/app/design/frontend/default/blank/template/callouts/right_col.phtml +++ b/app/design/frontend/default/blank/template/callouts/right_col.phtml @@ -26,6 +26,6 @@ ?> - + diff --git a/app/design/frontend/default/blank/template/catalog/navigation/top.phtml b/app/design/frontend/default/blank/template/catalog/navigation/top.phtml index c22deabf66..f7287bc29d 100644 --- a/app/design/frontend/default/blank/template/catalog/navigation/top.phtml +++ b/app/design/frontend/default/blank/template/catalog/navigation/top.phtml @@ -24,17 +24,17 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> - -getStoreCategories())>1): ?> +getStoreCategories()) > 0): ?> getStoreCategories() as $_category): ?> drawItem($_category) ?> - + diff --git a/app/design/frontend/default/blank/template/catalog/product/compare/list.phtml b/app/design/frontend/default/blank/template/catalog/product/compare/list.phtml index abae5f576f..61d3ac8d71 100644 --- a/app/design/frontend/default/blank/template/catalog/product/compare/list.phtml +++ b/app/design/frontend/default/blank/template/catalog/product/compare/list.phtml @@ -65,7 +65,7 @@ getReviewsSummaryHtml($_item, 'short') ?> getPriceHtml($_item, true) ?> isSaleable()): ?> - __('Add to Cart') ?> + __('Add to Cart') ?> __('Out of stock') ?> @@ -82,7 +82,7 @@ getItems() as $_item): ?> - __($_attribute->getFrontendLabel()) ?> + getStoreLabel() ?> getAttributeCode()) { @@ -111,7 +111,7 @@ getPriceHtml($_item) ?> isSaleable()): ?> - __('Add to Cart') ?> + __('Add to Cart') ?> __('Out of stock') ?> @@ -124,7 +124,7 @@ - __('Close Window') ?> + __('Close Window') ?> diff --git a/app/design/frontend/default/blank/template/catalog/product/compare/sidebar.phtml b/app/design/frontend/default/blank/template/catalog/product/compare/sidebar.phtml index 368a2ec873..156456c083 100644 --- a/app/design/frontend/default/blank/template/catalog/product/compare/sidebar.phtml +++ b/app/design/frontend/default/blank/template/catalog/product/compare/sidebar.phtml @@ -47,7 +47,7 @@ __('Clear All') ?> - __('Compare Items') ?> + __('Compare Items') ?> __('You have no items to compare.') ?> diff --git a/app/design/frontend/default/blank/template/catalog/product/list.phtml b/app/design/frontend/default/blank/template/catalog/product/list.phtml index 858576b3c5..9fef17a66b 100644 --- a/app/design/frontend/default/blank/template/catalog/product/list.phtml +++ b/app/design/frontend/default/blank/template/catalog/product/list.phtml @@ -56,7 +56,7 @@ getPriceHtml($_product, true) ?> isSaleable()): ?> - __('Add to Cart') ?> + __('Add to Cart') ?> __('Out of stock') ?> @@ -98,7 +98,7 @@ getPriceHtml($_product, true) ?> isSaleable()): ?> - __('Add to Cart') ?> + __('Add to Cart') ?> __('Out of stock') ?> diff --git a/app/design/frontend/default/blank/template/catalog/product/new.phtml b/app/design/frontend/default/blank/template/catalog/product/new.phtml index 7bf85d39ae..d052b9391c 100644 --- a/app/design/frontend/default/blank/template/catalog/product/new.phtml +++ b/app/design/frontend/default/blank/template/catalog/product/new.phtml @@ -38,7 +38,7 @@ getReviewsSummaryHtml($_product, 'short') ?> getPriceHtml($_product, true, '-new') ?> isSaleable()): ?> - __('Add to Cart') ?> + __('Add to Cart') ?> __('Out of stock') ?> diff --git a/app/design/frontend/default/blank/template/catalog/product/view/addtocart.phtml b/app/design/frontend/default/blank/template/catalog/product/view/addtocart.phtml index fca8b5f0b3..cfa374cc0c 100644 --- a/app/design/frontend/default/blank/template/catalog/product/view/addtocart.phtml +++ b/app/design/frontend/default/blank/template/catalog/product/view/addtocart.phtml @@ -34,6 +34,6 @@ __('Qty') ?>: - __('Add to Cart') ?> + __('Add to Cart') ?> \ No newline at end of file diff --git a/app/design/frontend/default/blank/template/catalogsearch/advanced/form.phtml b/app/design/frontend/default/blank/template/catalogsearch/advanced/form.phtml index e91a3e98d2..9438ffa4ac 100644 --- a/app/design/frontend/default/blank/template/catalogsearch/advanced/form.phtml +++ b/app/design/frontend/default/blank/template/catalogsearch/advanced/form.phtml @@ -78,9 +78,8 @@ - * __('Required Fields') ?> - __('Search') ?> + __('Search') ?> diff --git a/app/design/frontend/default/blank/template/catalogsearch/form.mini.phtml b/app/design/frontend/default/blank/template/catalogsearch/form.mini.phtml index 17a2f81c85..493df95c82 100644 --- a/app/design/frontend/default/blank/template/catalogsearch/form.mini.phtml +++ b/app/design/frontend/default/blank/template/catalogsearch/form.mini.phtml @@ -29,7 +29,7 @@ __('Search Site') ?> __('Search:') ?> - __('Search') ?> + __('Search') ?> « __('Back to Shopping Cart') ?> - isContinueDisabled()):?> disabled="disabled">__('Continue to Shipping Information') ?> + isContinueDisabled()):?> disabled="disabled">__('Continue to Shipping Information') ?> diff --git a/app/design/frontend/default/blank/template/checkout/multishipping/billing.phtml b/app/design/frontend/default/blank/template/checkout/multishipping/billing.phtml index 68b6dcaf68..f37c2926f8 100644 --- a/app/design/frontend/default/blank/template/checkout/multishipping/billing.phtml +++ b/app/design/frontend/default/blank/template/checkout/multishipping/billing.phtml @@ -76,7 +76,7 @@ « __('Back to Shipping Information') ?> - __('Continue to Review Your Order') ?> + __('Continue to Review Your Order') ?> diff --git a/app/design/frontend/default/blank/template/checkout/onepage/link.phtml b/app/design/frontend/default/blank/template/checkout/onepage/link.phtml index 7c245a842d..2c381434c1 100644 --- a/app/design/frontend/default/blank/template/checkout/onepage/link.phtml +++ b/app/design/frontend/default/blank/template/checkout/onepage/link.phtml @@ -26,6 +26,6 @@ ?> isPossibleOnepageCheckout()):?> - isDisabled()):?> disabled="disabled" onclick="window.location='getCheckoutUrl() ?>';">__('Proceed to Checkout') ?> + isDisabled()):?> disabled="disabled" onclick="window.location='getCheckoutUrl() ?>';">__('Proceed to Checkout') ?> diff --git a/app/design/frontend/default/blank/template/checkout/onepage/login.phtml b/app/design/frontend/default/blank/template/checkout/onepage/login.phtml index 42f8022a2a..6fce3491ef 100644 --- a/app/design/frontend/default/blank/template/checkout/onepage/login.phtml +++ b/app/design/frontend/default/blank/template/checkout/onepage/login.phtml @@ -108,13 +108,13 @@ - getQuote()->isAllowedGuestCheckout() ? $this->__('Continue') : $this->__('Register')) ?> + getQuote()->isAllowedGuestCheckout() ? $this->__('Continue') : $this->__('Register')) ?> __('Forgot your password?') ?> - __('Login') ?> + __('Login') ?> diff --git a/app/design/frontend/default/blank/template/checkout/onepage/payment.phtml b/app/design/frontend/default/blank/template/checkout/onepage/payment.phtml index 8219d8ca7b..79446b94aa 100644 --- a/app/design/frontend/default/blank/template/checkout/onepage/payment.phtml +++ b/app/design/frontend/default/blank/template/checkout/onepage/payment.phtml @@ -41,7 +41,7 @@ __('* Required Fields') ?> « __('Back') ?> - __('Continue') ?> + __('Continue') ?> __('Loading next step...') ?> diff --git a/app/design/frontend/default/blank/template/checkout/onepage/review.phtml b/app/design/frontend/default/blank/template/checkout/onepage/review.phtml index c663fcaaf4..620e3febe3 100644 --- a/app/design/frontend/default/blank/template/checkout/onepage/review.phtml +++ b/app/design/frontend/default/blank/template/checkout/onepage/review.phtml @@ -30,7 +30,7 @@ getChildHtml('agreements') ?> __('Forgot an Item?') ?> __('Edit Your Cart') ?> - __('Place Order') ?> + __('Place Order') ?> __('Submitting order information...') ?> diff --git a/app/design/frontend/default/blank/template/checkout/onepage/shipping.phtml b/app/design/frontend/default/blank/template/checkout/onepage/shipping.phtml index f8b16ec48a..26f686e8c2 100644 --- a/app/design/frontend/default/blank/template/checkout/onepage/shipping.phtml +++ b/app/design/frontend/default/blank/template/checkout/onepage/shipping.phtml @@ -91,7 +91,7 @@ __('* Required Fields') ?> « __('Back') ?> - __('Continue') ?> + __('Continue') ?> __('Loading next step...') ?> @@ -107,6 +107,6 @@ //shippingForm.setElementsRelation('shipping:country_id', 'shipping:region', 'getUrl('directory/json/childRegion') ?>', '__('Select State/Province...') ?>'); $('shipping-address-select') && shipping.newAddress(!$('shipping-address-select').value); - var shippingRegionUpdater = new RegionUpdater('shipping:country_id', 'shipping:region', 'shipping:region_id', countryRegions, undefined, helper('directory')->getCountriesWithOptionalZipJson() ?>); + var shippingRegionUpdater = new RegionUpdater('shipping:country_id', 'shipping:region', 'shipping:region_id', countryRegions, undefined, 'shipping:postcode'); //]]> diff --git a/app/design/frontend/default/blank/template/checkout/onepage/shipping_method.phtml b/app/design/frontend/default/blank/template/checkout/onepage/shipping_method.phtml index 0deb403107..2e3f9d4f14 100644 --- a/app/design/frontend/default/blank/template/checkout/onepage/shipping_method.phtml +++ b/app/design/frontend/default/blank/template/checkout/onepage/shipping_method.phtml @@ -39,7 +39,7 @@ « __('Back') ?> - __('Continue') ?> + __('Continue') ?> __('Loading next step...') ?> diff --git a/app/design/frontend/default/blank/template/checkout/success.phtml b/app/design/frontend/default/blank/template/checkout/success.phtml index e11b3252cf..b7e251d08e 100644 --- a/app/design/frontend/default/blank/template/checkout/success.phtml +++ b/app/design/frontend/default/blank/template/checkout/success.phtml @@ -42,5 +42,5 @@ - __('Continue Shopping') ?> + __('Continue Shopping') ?> diff --git a/app/design/frontend/default/blank/template/chronopay/info.phtml b/app/design/frontend/default/blank/template/chronopay/info.phtml index d566bab32d..c0e911ce48 100644 --- a/app/design/frontend/default/blank/template/chronopay/info.phtml +++ b/app/design/frontend/default/blank/template/chronopay/info.phtml @@ -28,8 +28,7 @@ getMethod()->getTitle() ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> \ No newline at end of file diff --git a/app/design/frontend/default/blank/template/contacts/form.phtml b/app/design/frontend/default/blank/template/contacts/form.phtml index be10570419..c3bc2c748b 100644 --- a/app/design/frontend/default/blank/template/contacts/form.phtml +++ b/app/design/frontend/default/blank/template/contacts/form.phtml @@ -57,7 +57,7 @@ __('* Required Fields') ?> - __('Submit') ?> + __('Submit') ?> diff --git a/app/design/frontend/default/blank/template/customer/form/address.phtml b/app/design/frontend/default/blank/template/customer/form/address.phtml index 4874cdbbd6..6e42662b6c 100644 --- a/app/design/frontend/default/blank/template/customer/form/address.phtml +++ b/app/design/frontend/default/blank/template/customer/form/address.phtml @@ -111,13 +111,13 @@ __('* Required Fields') ?> « __('Back') ?> - __('Save Address') ?> + __('Save Address') ?> \ No newline at end of file + diff --git a/app/design/frontend/default/blank/template/customer/form/changepassword.phtml b/app/design/frontend/default/blank/template/customer/form/changepassword.phtml index 31a11e01bc..7a8b155c1d 100644 --- a/app/design/frontend/default/blank/template/customer/form/changepassword.phtml +++ b/app/design/frontend/default/blank/template/customer/form/changepassword.phtml @@ -50,7 +50,7 @@ __('* Required Fields') ?> « __('Back') ?> - __('Save Password') ?> + __('Save Password') ?> diff --git a/app/design/frontend/default/blank/template/customer/widget/gender.phtml b/app/design/frontend/default/blank/template/customer/widget/gender.phtml new file mode 100644 index 0000000000..ddee19ee58 --- /dev/null +++ b/app/design/frontend/default/blank/template/customer/widget/gender.phtml @@ -0,0 +1,34 @@ + +isRequired()) echo ' class="required"' ?>>isRequired()) echo '*' ?>__('Gender') ?> +getFieldParams() ?>"> + getAttribute('gender')->getSource()->getAllOptions();?> + getGender();?> + + > + + \ No newline at end of file diff --git a/app/design/frontend/default/blank/template/cybersource/form.phtml b/app/design/frontend/default/blank/template/cybersource/form.phtml index ca554f0c99..75b7c6102c 100644 --- a/app/design/frontend/default/blank/template/cybersource/form.phtml +++ b/app/design/frontend/default/blank/template/cybersource/form.phtml @@ -106,6 +106,7 @@ + diff --git a/app/design/frontend/default/blank/template/page/2columns-right.phtml b/app/design/frontend/default/blank/template/page/2columns-right.phtml index b5c704214f..3d53f64da7 100644 --- a/app/design/frontend/default/blank/template/page/2columns-right.phtml +++ b/app/design/frontend/default/blank/template/page/2columns-right.phtml @@ -41,7 +41,7 @@ getChildHtml('header') ?> getChildHtml('breadcrumbs') ?> - getChildHtml('right') ?> + getChildHtml('right') ?> getChildHtml('global_messages') ?> getChildHtml('content') ?> diff --git a/app/design/frontend/default/blank/template/page/print.phtml b/app/design/frontend/default/blank/template/page/print.phtml index 72bcbb64c4..1120f6e507 100644 --- a/app/design/frontend/default/blank/template/page/print.phtml +++ b/app/design/frontend/default/blank/template/page/print.phtml @@ -44,7 +44,7 @@ getChildHtml('content') ?> - __('Close Window') ?> + __('Close Window') ?> getAbsoluteFooter() ?> diff --git a/app/design/frontend/default/blank/template/paybox/direct/info.phtml b/app/design/frontend/default/blank/template/paybox/direct/info.phtml index 19624389db..d9d9a4c1f0 100644 --- a/app/design/frontend/default/blank/template/paybox/direct/info.phtml +++ b/app/design/frontend/default/blank/template/paybox/direct/info.phtml @@ -28,8 +28,7 @@ getMethod()->getTitle() ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> - __('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> - __('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> + __('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> diff --git a/app/design/frontend/default/blank/template/payment/info/cc.phtml b/app/design/frontend/default/blank/template/payment/info/cc.phtml index 8c619f73e1..d59fc95679 100644 --- a/app/design/frontend/default/blank/template/payment/info/cc.phtml +++ b/app/design/frontend/default/blank/template/payment/info/cc.phtml @@ -27,8 +27,7 @@ getInfo()): ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> - __('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> - __('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> + __('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> diff --git a/app/design/frontend/default/blank/template/payment/info/ccsave.phtml b/app/design/frontend/default/blank/template/payment/info/ccsave.phtml index 3ef7be67c9..01d200bf09 100644 --- a/app/design/frontend/default/blank/template/payment/info/ccsave.phtml +++ b/app/design/frontend/default/blank/template/payment/info/ccsave.phtml @@ -26,5 +26,4 @@ ?> __('Name on the Card: %s', $this->htmlEscape($this->getInfo()->getCcOwner())) ?> __('Credit Card Type: %s', $this->htmlEscape($this->getCcTypeName())) ?> -__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> -__('Expiration Date: %s/%s', $this->htmlEscape($this->getCcExpMonth()), $this->htmlEscape($this->getInfo()->getCcExpYear())) ?> +__('Credit Card Number: xxxx-%s', $this->htmlEscape($this->getInfo()->getCcLast4())) ?> diff --git a/app/design/frontend/default/blank/template/paypal/express/review.phtml b/app/design/frontend/default/blank/template/paypal/express/review.phtml index 306e60faac..2277b97eb7 100644 --- a/app/design/frontend/default/blank/template/paypal/express/review.phtml +++ b/app/design/frontend/default/blank/template/paypal/express/review.phtml @@ -75,7 +75,7 @@ - __('Update Shipping Method') ?> + __('Update Shipping Method') ?> @@ -104,7 +104,7 @@ - __('Place an Order') ?> + __('Place an Order') ?> __('Submitting order information...') ?> diff --git a/app/design/frontend/default/blank/template/paypaluk/direct/form.phtml b/app/design/frontend/default/blank/template/paypaluk/direct/form.phtml index a39ebc47c6..489c74d431 100644 --- a/app/design/frontend/default/blank/template/paypaluk/direct/form.phtml +++ b/app/design/frontend/default/blank/template/paypaluk/direct/form.phtml @@ -34,25 +34,31 @@ __('Switch/Solo Only') ?> * - - __('Issue Number') ?>: - - __('Start Date') ?>: - - getCcMonths() as $k=>$v): ?> - getInfoData('cc_ss_start_month')): ?> selected="selected"> - - - - getSsStartYears() as $k=>$v): ?> - getInfoData('cc_ss_start_year')): ?> selected="selected"> - - - - + + + __('Issue Number') ?>: + + + __('Start Date') ?>: + + + getCcMonths() as $k=>$v): ?> + getInfoData('cc_ss_start_month')): ?> selected="selected"> + + + + + + getSsStartYears() as $k=>$v): ?> + getInfoData('cc_ss_start_year')): ?> selected="selected"> + + + + + - + + diff --git a/app/design/frontend/default/blank/template/paypaluk/direct/info.phtml b/app/design/frontend/default/blank/template/paypaluk/direct/info.phtml index cb06ad7d09..f0d72f3397 100644 --- a/app/design/frontend/default/blank/template/paypaluk/direct/info.phtml +++ b/app/design/frontend/default/blank/template/paypaluk/direct/info.phtml @@ -28,7 +28,6 @@ __('Credit Card Type: %s', $this->getCcTypeName()) ?> __('Credit Card Number: xxxx-%s', $this->getInfo()->getCcLast4()) ?> - __('Expiration Date: %s/%s', $this->getCcExpMonth(), $this->getInfo()->getCcExpYear()) ?> getInfo()->getCcSsIssue()): ?> __("Switch/Solo card issue number: %s", $this->getInfo()->getCcSsIssue()) ?> diff --git a/app/design/frontend/default/blank/template/paypaluk/express/review.phtml b/app/design/frontend/default/blank/template/paypaluk/express/review.phtml index dcb84b4a52..d3910fdd73 100644 --- a/app/design/frontend/default/blank/template/paypaluk/express/review.phtml +++ b/app/design/frontend/default/blank/template/paypaluk/express/review.phtml @@ -75,10 +75,8 @@ - - __('Update Shipping Method') ?> + __('Update Shipping Method') ?> - @@ -105,7 +103,7 @@ __('Submitting order information...') ?> - __('Place an Order') ?> + __('Place an Order') ?> isSaleable()):?> - __('Add All to Cart') ?> + __('Add All to Cart') ?> diff --git a/app/design/frontend/default/blank/template/wishlist/sharing.phtml b/app/design/frontend/default/blank/template/wishlist/sharing.phtml index 52dad3df51..4beb2192a7 100644 --- a/app/design/frontend/default/blank/template/wishlist/sharing.phtml +++ b/app/design/frontend/default/blank/template/wishlist/sharing.phtml @@ -51,7 +51,7 @@ __('* Required Fields') ?> « __('Back')?> - __('Share Wishlist') ?> + __('Share Wishlist') ?> diff --git a/app/design/frontend/default/blank/template/wishlist/view.phtml b/app/design/frontend/default/blank/template/wishlist/view.phtml index 672fbf6482..eb226c12d0 100644 --- a/app/design/frontend/default/blank/template/wishlist/view.phtml +++ b/app/design/frontend/default/blank/template/wishlist/view.phtml @@ -68,7 +68,7 @@ isSaleable()): ?> - __('Add to Cart') ?> + __('Add to Cart') ?> @@ -80,11 +80,11 @@ - __('Share Wishlist') ?> + __('Share Wishlist') ?> isSaleable()):?> - __('Add All to Cart') ?> + __('Add All to Cart') ?> - __('Update Wishlist') ?> + __('Update Wishlist') ?> diff --git a/app/design/frontend/default/default/layout/checkout.xml b/app/design/frontend/default/default/layout/checkout.xml index e2075b77bb..f6be52be0a 100644 --- a/app/design/frontend/default/default/layout/checkout.xml +++ b/app/design/frontend/default/default/layout/checkout.xml @@ -48,6 +48,9 @@ Default layout, loads most of the pages configurablecheckout/cart_item_renderer_configurablecheckout/cart/sidebar/default.phtml + + + diff --git a/app/design/frontend/default/default/layout/customer.xml b/app/design/frontend/default/default/layout/customer.xml index aa01cc5684..320ee93024 100644 --- a/app/design/frontend/default/default/layout/customer.xml +++ b/app/design/frontend/default/default/layout/customer.xml @@ -119,6 +119,9 @@ New customer registration + + + page/1column.phtml @@ -238,6 +241,9 @@ Customer account address edit page + + + diff --git a/app/design/frontend/default/default/layout/ogone.xml b/app/design/frontend/default/default/layout/ogone.xml new file mode 100644 index 0000000000..d498775aa5 --- /dev/null +++ b/app/design/frontend/default/default/layout/ogone.xml @@ -0,0 +1,47 @@ + + + + + + + + page/1column.phtml + + + + + + + + + + + + + + diff --git a/app/design/frontend/default/default/template/callouts/right_col.phtml b/app/design/frontend/default/default/template/callouts/right_col.phtml index d812eed9a9..c34cdf544d 100644 --- a/app/design/frontend/default/default/template/callouts/right_col.phtml +++ b/app/design/frontend/default/default/template/callouts/right_col.phtml @@ -26,5 +26,5 @@ ?> - + diff --git a/app/design/frontend/default/default/template/catalog/layer/filter.phtml b/app/design/frontend/default/default/template/catalog/layer/filter.phtml index 05770dd59d..96b995330a 100644 --- a/app/design/frontend/default/default/template/catalog/layer/filter.phtml +++ b/app/design/frontend/default/default/template/catalog/layer/filter.phtml @@ -35,7 +35,10 @@ getItems() as $_item): ?> + getCount() > 0): ?> getLabel() ?> + getLabel() ?> + (getCount() ?>) diff --git a/app/design/frontend/default/default/template/catalog/navigation/top.phtml b/app/design/frontend/default/default/template/catalog/navigation/top.phtml index c009288e64..77efa554f4 100644 --- a/app/design/frontend/default/default/template/catalog/navigation/top.phtml +++ b/app/design/frontend/default/default/template/catalog/navigation/top.phtml @@ -24,7 +24,7 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> - __('Category Navigation:') ?> - - getStoreCategories() as $_category): ?> - drawItem($_category) ?> - - + getStoreCategories()) > 0): ?> + + getStoreCategories() as $_category): ?> + drawItem($_category) ?> + + + getChildHtml('topLeftLinks') ?>
getButtonsHtml('header') ?>
__('Thank you for your purchase!') ?>
__('You will receive an order confirmation email with details of your order and a link to track its progress.') ?>
__('Add to Cart') ?>
__('Out of stock') ?>
__('You have no items to compare.') ?>
* __('Required Fields') ?>
__('* Required Fields') ?>
__('Forgot an Item?') ?> __('Edit Your Cart') ?>
- __('Update Shipping Method') ?> + __('Update Shipping Method') ?>
__('Update Shipping Method') ?>
__('In stock') ?>
__('Tag Name: %s', $this->htmlEscape($this->getTagInfo()->getName())) ?> - ( __('Edit Tag') ?> - | - __('Delete Tag') ?> ) + __('Delete') ?>
getDescription() ?>
htmlEscape($this->getMethod()->getTitle()) ?>
getTracksCollection()->count()) : ?> - __('Track all shipments') ?> | + __('Track all shipments') ?> | __('Print All Shipments') ?>
en_US
' + o.content + '
]*>( | |\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.undoManager.add({initial : true}); + 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 = EditorManager.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. + */ + 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, + strict_root : 1, + fix_ie_paragraphs : 1, + update_styles : 1 + }); + + 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, + convert_fonts_to_spans : s.convert_fonts_to_spans, + font_size_classes : s.font_size_classes, + font_size_style_values : s.font_size_style_values, + apply_source_formatting : s.apply_source_formatting, + dom : t.dom + }); + + 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); + + if (s.convert_fonts_to_spans) + t._convertFonts(); + + if (s.inline_styles) + t._convertInlineElements(); + + 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 EditorManager + * it will also place DOM focus inside the editor. + * + * @param {bool} 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 or the selection is outside the area in IE + // the IE statement is needed to avoid bluring if element selections inside layers since + // the layer is like it's own document in IE + if (!ce && (!isIE || t.selection.getNode().ownerDocument != t.getDoc())) + 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 (EditorManager.activeEditor != t) { + if ((oed = EditorManager.activeEditor) != null) + oed.onDeactivate.dispatch(oed, t); + + t.onActivate.dispatch(t, oed); + } + + EditorManager._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. + * + * @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. + * + * @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 = EditorManager.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. + * + * @param {String} n Name/key to get from the language pack. + * @param {String} dv Optional default value to retrive. + */ + getLang : function(n, dv) { + return EditorManager.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); + }, + + /** + * Returns a configuration parameter by name. + * + * @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. + * + * @param {Object} o Optional object to pass along for the node changed event. + */ + nodeChanged : function(o) { + var t = this, s = t.selection, n = s.getNode() || t.getBody(); + + // Fix for bug #1896577 it seems that this can not be fired while the editor is loading + if (t.initialized) { + t.onNodeChange.dispatch( + t, + o ? o.controlManager || t.controlManager : t.controlManager, + isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n, // Fix for IE initial state + 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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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 {bool} 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. + * + * @param {String} cmd Command name to execute, for example mceLink or Bold. + * @param {bool} 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 {bool} 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. + * + * @param {string} c Command to query state from. + * @return {bool} Command specific state, for example if bold is enabled or not. + */ + queryCommandState : function(c) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryStateCommands[c]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandState(c); + if (o !== -1) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandState(c); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + /** + * Returns a command specific value, for example the current font size. + * + * @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. + */ + 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. + */ + 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. + * + * @return {bool} 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. + * + * @param {bool} 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 {bool} 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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @return {bool} 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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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 EditorManager collection. + */ + 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 = []; + + EditorManager.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. + * + * @param {bool} 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); + } + + // 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, '']); + + 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) { + t.selection.getRng().item(0).removeNode(); + 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(); + }; + + // Add undo level on editor blur + if (tinymce.isIE) { + t.dom.bind(t.getWin(), 'blur', function(e) { + var n; + + // Check added for fullscreen bug + if (t.selection) { + n = t.selection.getNode(); + + // Add undo level is selection was lost to another document + if (!t.removed && n.ownerDocument && n.ownerDocument != t.getDoc()) + addUndo(); + } + }); + } else { + t.dom.bind(t.getDoc(), 'blur', function() { + if (t.selection && !t.removed) + addUndo(); + }); + } + + t.onMouseDown.add(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) { + t.undoManager.typing = 0; + t.undoManager.add(); + } + }); + + 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) { + t.undoManager.add(); + t.undoManager.typing = 0; + } + + return; + } + + if (!t.undoManager.typing) { + t.undoManager.add(); + t.undoManager.typing = 1; + } + }); + } + }, + + _convertInlineElements : function() { + var t = this, s = t.settings, dom = t.dom, v, e, na, st, sp; + + function convert(ed, o) { + if (!s.inline_styles) + return; + + if (o.get) { + each(t.dom.select('table,u,strike', o.node), function(n) { + switch (n.nodeName) { + case 'TABLE': + if (v = dom.getAttrib(n, 'height')) { + dom.setStyle(n, 'height', v); + dom.setAttrib(n, 'height', ''); + } + break; + + case 'U': + case 'STRIKE': + //sp = dom.create('span', {style : dom.getAttrib(n, 'style')}); + n.style.textDecoration = n.nodeName == 'U' ? 'underline' : 'line-through'; + dom.setAttrib(n, 'mce_style', ''); + dom.setAttrib(n, 'mce_name', 'span'); + break; + } + }); + } else if (o.set) { + each(t.dom.select('table,span', o.node).reverse(), function(n) { + if (n.nodeName == 'TABLE') { + if (v = dom.getStyle(n, 'height')) + dom.setAttrib(n, 'height', v.replace(/[^0-9%]+/g, '')); + } else { + // Convert spans to elements + if (n.style.textDecoration == 'underline') + na = 'u'; + else if (n.style.textDecoration == 'line-through') + na = 'strike'; + else + na = ''; + + if (na) { + n.style.textDecoration = ''; + dom.setAttrib(n, 'mce_style', ''); + + e = dom.create(na, { + style : dom.getAttrib(n, 'style') + }); + + dom.replace(e, n, 1); + } + } + }); + } + }; + + t.onPreProcess.add(convert); + + if (!s.cleanup_on_startup) { + t.onSetContent.add(function(ed, o) { + if (o.initial) + convert(t, {node : t.getBody(), set : 1}); + }); + } + }, + + _convertFonts : function() { + var t = this, s = t.settings, dom = t.dom, fz, fzn, sl, cl; + + // No need + if (!s.inline_styles) + return; + + // Font pt values and font size names + fz = [8, 10, 12, 14, 18, 24, 36]; + fzn = ['xx-small', 'x-small','small','medium','large','x-large', 'xx-large']; + + if (sl = s.font_size_style_values) + sl = explode(sl); + + if (cl = s.font_size_classes) + cl = explode(cl); + + function process(no) { + var n, sp, nl, x; + + // Keep unit tests happy + if (!s.inline_styles) + return; + + nl = t.dom.select('font', no); + for (x = nl.length - 1; x >= 0; x--) { + n = nl[x]; + + sp = dom.create('span', { + style : dom.getAttrib(n, 'style'), + 'class' : dom.getAttrib(n, 'class') + }); + + dom.setStyles(sp, { + fontFamily : dom.getAttrib(n, 'face'), + color : dom.getAttrib(n, 'color'), + backgroundColor : n.style.backgroundColor + }); + + if (n.size) { + if (sl) + dom.setStyle(sp, 'fontSize', sl[parseInt(n.size) - 1]); + else + dom.setAttrib(sp, 'class', cl[parseInt(n.size) - 1]); + } + + dom.setAttrib(sp, 'mce_style', ''); + dom.replace(sp, n, 1); + } + }; + + // Run on cleanup + t.onPreProcess.add(function(ed, o) { + if (o.get) + process(o.node); + }); + + t.onSetContent.add(function(ed, o) { + if (o.initial) + process(o.node); + }); + }, + + _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[d.length - 1].tag + '>'; + 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 += '' + d[i].tag + '>'; + + return s; + } + + /**#@-*/ + }); +})(tinymce); diff --git a/js/tiny_mce/classes/EditorCommands.js b/js/tiny_mce/classes/EditorCommands.js new file mode 100644 index 0000000000..d9997dcec3 --- /dev/null +++ b/js/tiny_mce/classes/EditorCommands.js @@ -0,0 +1,934 @@ +/** + * $Id: EditorCommands.js 1070 2009-04-01 18:03:06Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + var each = tinymce.each, isIE = tinymce.isIE, isGecko = tinymce.isGecko, isOpera = tinymce.isOpera, isWebKit = tinymce.isWebKit; + + /** + * This is a internal class and no method in this class should be called directly form the out side. + */ + tinymce.create('tinymce.EditorCommands', { + EditorCommands : function(ed) { + this.editor = ed; + }, + + execCommand : function(cmd, ui, val) { + var t = this, ed = t.editor, f; + + switch (cmd) { + // Ignore these + case 'mceResetDesignMode': + case 'mceBeginUndoLevel': + return true; + + // Ignore these + case 'unlink': + t.UnLink(); + return true; + + // Bundle these together + case 'JustifyLeft': + case 'JustifyCenter': + case 'JustifyRight': + case 'JustifyFull': + t.mceJustify(cmd, cmd.substring(7).toLowerCase()); + return true; + + default: + f = this[cmd]; + + if (f) { + f.call(this, ui, val); + return true; + } + } + + return false; + }, + + Indent : function() { + var ed = this.editor, d = ed.dom, s = ed.selection, e, iv, iu; + + // Setup indent level + iv = ed.settings.indentation; + iu = /[a-z%]+$/i.exec(iv); + iv = parseInt(iv); + + if (ed.settings.inline_styles && (!this.queryStateInsertUnorderedList() && !this.queryStateInsertOrderedList())) { + each(s.getSelectedBlocks(), function(e) { + d.setStyle(e, 'paddingLeft', (parseInt(e.style.paddingLeft || 0) + iv) + iu); + }); + + return; + } + + ed.getDoc().execCommand('Indent', false, null); + + if (isIE) { + d.getParent(s.getNode(), function(n) { + if (n.nodeName == 'BLOCKQUOTE') { + n.dir = n.style.cssText = ''; + } + }); + } + }, + + Outdent : function() { + var ed = this.editor, d = ed.dom, s = ed.selection, e, v, iv, iu; + + // Setup indent level + iv = ed.settings.indentation; + iu = /[a-z%]+$/i.exec(iv); + iv = parseInt(iv); + + if (ed.settings.inline_styles && (!this.queryStateInsertUnorderedList() && !this.queryStateInsertOrderedList())) { + each(s.getSelectedBlocks(), function(e) { + v = Math.max(0, parseInt(e.style.paddingLeft || 0) - iv); + d.setStyle(e, 'paddingLeft', v ? v + iu : ''); + }); + + return; + } + + ed.getDoc().execCommand('Outdent', false, null); + }, + +/* + mceSetAttribute : function(u, v) { + var ed = this.editor, d = ed.dom, e; + + if (e = d.getParent(ed.selection.getNode(), d.isBlock)) + d.setAttrib(e, v.name, v.value); + }, +*/ + mceSetContent : function(u, v) { + this.editor.setContent(v); + }, + + mceToggleVisualAid : function() { + var ed = this.editor; + + ed.hasVisual = !ed.hasVisual; + ed.addVisual(); + }, + + mceReplaceContent : function(u, v) { + var s = this.editor.selection; + + s.setContent(v.replace(/\{\$selection\}/g, s.getContent({format : 'text'}))); + }, + + mceInsertLink : function(u, v) { + var ed = this.editor, s = ed.selection, e = ed.dom.getParent(s.getNode(), 'a'); + + if (tinymce.is(v, 'string')) + v = {href : v}; + + function set(e) { + each(v, function(v, k) { + ed.dom.setAttrib(e, k, v); + }); + }; + + if (!e) { + ed.execCommand('CreateLink', false, 'javascript:mctmp(0);'); + each(ed.dom.select('a[href=javascript:mctmp(0);]'), function(e) { + set(e); + }); + } else { + if (v.href) + set(e); + else + ed.dom.remove(e, 1); + } + }, + + UnLink : function() { + var ed = this.editor, s = ed.selection; + + if (s.isCollapsed()) + s.select(s.getNode()); + + ed.getDoc().execCommand('unlink', false, null); + s.collapse(0); + }, + + FontName : function(u, v) { + var t = this, ed = t.editor, s = ed.selection, e; + + if (!v) { + if (s.isCollapsed()) + s.select(s.getNode()); + } else { + if (ed.settings.convert_fonts_to_spans) + t._applyInlineStyle('span', {style : {fontFamily : v}}); + else + ed.getDoc().execCommand('FontName', false, v); + } + }, + + FontSize : function(u, v) { + var ed = this.editor, s = ed.settings, fc, fs; + + // Use style options instead + if (s.convert_fonts_to_spans && v >= 1 && v <= 7) { + fs = tinymce.explode(s.font_size_style_values); + fc = tinymce.explode(s.font_size_classes); + + if (fc) + v = fc[v - 1] || v; + else + v = fs[v - 1] || v; + } + + if (v >= 1 && v <= 7) + ed.getDoc().execCommand('FontSize', false, v); + else + this._applyInlineStyle('span', {style : {fontSize : v}}); + }, + + queryCommandValue : function(c) { + var f = this['queryValue' + c]; + + if (f) + return f.call(this, c); + + return false; + }, + + queryCommandState : function(cmd) { + var f; + + switch (cmd) { + // Bundle these together + case 'JustifyLeft': + case 'JustifyCenter': + case 'JustifyRight': + case 'JustifyFull': + return this.queryStateJustify(cmd, cmd.substring(7).toLowerCase()); + + default: + if (f = this['queryState' + cmd]) + return f.call(this, cmd); + } + + return -1; + }, + + _queryState : function(c) { + try { + return this.editor.getDoc().queryCommandState(c); + } catch (ex) { + // Ignore exception + } + }, + + _queryVal : function(c) { + try { + return this.editor.getDoc().queryCommandValue(c); + } catch (ex) { + // Ignore exception + } + }, + + queryValueFontSize : function() { + var ed = this.editor, v = 0, p; + + if (p = ed.dom.getParent(ed.selection.getNode(), 'span')) + v = p.style.fontSize; + + if (!v && (isOpera || isWebKit)) { + if (p = ed.dom.getParent(ed.selection.getNode(), 'font')) + v = p.size; + + return v; + } + + return v || this._queryVal('FontSize'); + }, + + queryValueFontName : function() { + var ed = this.editor, v = 0, p; + + if (p = ed.dom.getParent(ed.selection.getNode(), 'font')) + v = p.face; + + if (p = ed.dom.getParent(ed.selection.getNode(), 'span')) + v = p.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); + + if (!v) + v = this._queryVal('FontName'); + + return v; + }, + + mceJustify : function(c, v) { + var ed = this.editor, se = ed.selection, n = se.getNode(), nn = n.nodeName, bl, nb, dom = ed.dom, rm; + + if (ed.settings.inline_styles && this.queryStateJustify(c, v)) + rm = 1; + + bl = dom.getParent(n, ed.dom.isBlock); + + if (nn == 'IMG') { + if (v == 'full') + return; + + if (rm) { + if (v == 'center') + dom.setStyle(bl || n.parentNode, 'textAlign', ''); + + dom.setStyle(n, 'float', ''); + this.mceRepaint(); + return; + } + + if (v == 'center') { + // Do not change table elements + if (bl && /^(TD|TH)$/.test(bl.nodeName)) + bl = 0; + + if (!bl || bl.childNodes.length > 1) { + nb = dom.create('p'); + nb.appendChild(n.cloneNode(false)); + + if (bl) + dom.insertAfter(nb, bl); + else + dom.insertAfter(nb, n); + + dom.remove(n); + n = nb.firstChild; + bl = nb; + } + + dom.setStyle(bl, 'textAlign', v); + dom.setStyle(n, 'float', ''); + } else { + dom.setStyle(n, 'float', v); + dom.setStyle(bl || n.parentNode, 'textAlign', ''); + } + + this.mceRepaint(); + return; + } + + // Handle the alignment outselfs, less quirks in all browsers + if (ed.settings.inline_styles && ed.settings.forced_root_block) { + if (rm) + v = ''; + + each(se.getSelectedBlocks(dom.getParent(se.getStart(), dom.isBlock), dom.getParent(se.getEnd(), dom.isBlock)), function(e) { + dom.setAttrib(e, 'align', ''); + dom.setStyle(e, 'textAlign', v == 'full' ? 'justify' : v); + }); + + return; + } else if (!rm) + ed.getDoc().execCommand(c, false, null); + + if (ed.settings.inline_styles) { + if (rm) { + dom.getParent(ed.selection.getNode(), function(n) { + if (n.style && n.style.textAlign) + dom.setStyle(n, 'textAlign', ''); + }); + + return; + } + + each(dom.select('*'), function(n) { + var v = n.align; + + if (v) { + if (v == 'full') + v = 'justify'; + + dom.setStyle(n, 'textAlign', v); + dom.setAttrib(n, 'align', ''); + } + }); + } + }, + + mceSetCSSClass : function(u, v) { + this.mceSetStyleInfo(0, {command : 'setattrib', name : 'class', value : v}); + }, + + getSelectedElement : function() { + var t = this, ed = t.editor, dom = ed.dom, se = ed.selection, r = se.getRng(), r1, r2, sc, ec, so, eo, e, sp, ep, re; + + if (se.isCollapsed() || r.item) + return se.getNode(); + + // Setup regexp + re = ed.settings.merge_styles_invalid_parents; + if (tinymce.is(re, 'string')) + re = new RegExp(re, 'i'); + + if (isIE) { + r1 = r.duplicate(); + r1.collapse(true); + sc = r1.parentElement(); + + r2 = r.duplicate(); + r2.collapse(false); + ec = r2.parentElement(); + + if (sc != ec) { + r1.move('character', 1); + sc = r1.parentElement(); + } + + if (sc == ec) { + r1 = r.duplicate(); + r1.moveToElementText(sc); + + if (r1.compareEndPoints('StartToStart', r) == 0 && r1.compareEndPoints('EndToEnd', r) == 0) + return re && re.test(sc.nodeName) ? null : sc; + } + } else { + function getParent(n) { + return dom.getParent(n, '*'); + }; + + sc = r.startContainer; + ec = r.endContainer; + so = r.startOffset; + eo = r.endOffset; + + if (!r.collapsed) { + if (sc == ec) { + if (so - eo < 2) { + if (sc.hasChildNodes()) { + sp = sc.childNodes[so]; + return re && re.test(sp.nodeName) ? null : sp; + } + } + } + } + + if (sc.nodeType != 3 || ec.nodeType != 3) + return null; + + if (so == 0) { + sp = getParent(sc); + + if (sp && sp.firstChild != sc) + sp = null; + } + + if (so == sc.nodeValue.length) { + e = sc.nextSibling; + + if (e && e.nodeType == 1) + sp = sc.nextSibling; + } + + if (eo == 0) { + e = ec.previousSibling; + + if (e && e.nodeType == 1) + ep = e; + } + + if (eo == ec.nodeValue.length) { + ep = getParent(ec); + + if (ep && ep.lastChild != ec) + ep = null; + } + + // Same element + if (sp == ep) + return re && sp && re.test(sp.nodeName) ? null : sp; + } + + return null; + }, + + mceSetStyleInfo : function(u, v) { + var t = this, ed = t.editor, d = ed.getDoc(), dom = ed.dom, e, b, s = ed.selection, nn = v.wrapper || 'span', b = s.getBookmark(), re; + + function set(n, e) { + if (n.nodeType == 1) { + switch (v.command) { + case 'setattrib': + return dom.setAttrib(n, v.name, v.value); + + case 'setstyle': + return dom.setStyle(n, v.name, v.value); + + case 'removeformat': + return dom.setAttrib(n, 'class', ''); + } + } + }; + + // Setup regexp + re = ed.settings.merge_styles_invalid_parents; + if (tinymce.is(re, 'string')) + re = new RegExp(re, 'i'); + + // Set style info on selected element + if ((e = t.getSelectedElement()) && !ed.settings.force_span_wrappers) + set(e, 1); + else { + // Generate wrappers and set styles on them + d.execCommand('FontName', false, '__'); + each(dom.select('span,font'), function(n) { + var sp, e; + + if (dom.getAttrib(n, 'face') == '__' || n.style.fontFamily === '__') { + sp = dom.create(nn, {mce_new : '1'}); + + set(sp); + + each (n.childNodes, function(n) { + sp.appendChild(n.cloneNode(true)); + }); + + dom.replace(sp, n); + } + }); + } + + // Remove wrappers inside new ones + each(dom.select(nn).reverse(), function(n) { + var p = n.parentNode; + + // Check if it's an old span in a new wrapper + if (!dom.getAttrib(n, 'mce_new')) { + // Find new wrapper + p = dom.getParent(n, '*[mce_new]'); + + if (p) + dom.remove(n, 1); + } + }); + + // Merge wrappers with parent wrappers + each(dom.select(nn).reverse(), function(n) { + var p = n.parentNode; + + if (!p || !dom.getAttrib(n, 'mce_new')) + return; + + if (ed.settings.force_span_wrappers && p.nodeName != 'SPAN') + return; + + // Has parent of the same type and only child + if (p.nodeName == nn.toUpperCase() && p.childNodes.length == 1) + return dom.remove(p, 1); + + // Has parent that is more suitable to have the class and only child + if (n.nodeType == 1 && (!re || !re.test(p.nodeName)) && p.childNodes.length == 1) { + set(p); // Set style info on parent instead + dom.setAttrib(n, 'class', ''); + } + }); + + // Remove empty wrappers + each(dom.select(nn).reverse(), function(n) { + if (dom.getAttrib(n, 'mce_new') || (dom.getAttribs(n).length <= 1 && n.className === '')) { + if (!dom.getAttrib(n, 'class') && !dom.getAttrib(n, 'style')) + return dom.remove(n, 1); + + dom.setAttrib(n, 'mce_new', ''); // Remove mce_new marker + } + }); + + s.moveToBookmark(b); + }, + + queryStateJustify : function(c, v) { + var ed = this.editor, n = ed.selection.getNode(), dom = ed.dom; + + if (n && n.nodeName == 'IMG') { + if (dom.getStyle(n, 'float') == v) + return 1; + + return n.parentNode.style.textAlign == v; + } + + n = dom.getParent(ed.selection.getStart(), function(n) { + return n.nodeType == 1 && n.style.textAlign; + }); + + if (v == 'full') + v = 'justify'; + + if (ed.settings.inline_styles) + return (n && n.style.textAlign == v); + + return this._queryState(c); + }, + + ForeColor : function(ui, v) { + var ed = this.editor; + + if (ed.settings.convert_fonts_to_spans) { + this._applyInlineStyle('span', {style : {color : v}}); + return; + } else + ed.getDoc().execCommand('ForeColor', false, v); + }, + + HiliteColor : function(ui, val) { + var t = this, ed = t.editor, d = ed.getDoc(); + + if (ed.settings.convert_fonts_to_spans) { + this._applyInlineStyle('span', {style : {backgroundColor : val}}); + return; + } + + function set(s) { + if (!isGecko) + return; + + try { + // Try new Gecko method + d.execCommand("styleWithCSS", 0, s); + } catch (ex) { + // Use old + d.execCommand("useCSS", 0, !s); + } + }; + + if (isGecko || isOpera) { + set(true); + d.execCommand('hilitecolor', false, val); + set(false); + } else + d.execCommand('BackColor', false, val); + }, + + FormatBlock : function(ui, val) { + var t = this, ed = t.editor, s = ed.selection, dom = ed.dom, bl, nb, b; + + function isBlock(n) { + return /^(P|DIV|H[1-6]|ADDRESS|BLOCKQUOTE|PRE)$/.test(n.nodeName); + }; + + bl = dom.getParent(s.getNode(), function(n) { + return isBlock(n); + }); + + // IE has an issue where it removes the parent div if you change format on the paragrah in Content + // FF and Opera doesn't change parent DIV elements if you switch format + if (bl) { + if ((isIE && isBlock(bl.parentNode)) || bl.nodeName == 'DIV') { + // Rename block element + nb = ed.dom.create(val); + + each(dom.getAttribs(bl), function(v) { + dom.setAttrib(nb, v.nodeName, dom.getAttrib(bl, v.nodeName)); + }); + + b = s.getBookmark(); + dom.replace(nb, bl, 1); + s.moveToBookmark(b); + ed.nodeChanged(); + return; + } + } + + val = ed.settings.forced_root_block ? (val || '') : val; + + if (val.indexOf('<') == -1) + val = '<' + val + '>'; + + if (tinymce.isGecko) + val = val.replace(/<(div|blockquote|code|dt|dd|dl|samp)>/gi, '$1'); + + ed.getDoc().execCommand('FormatBlock', false, val); + }, + + mceCleanup : function() { + var ed = this.editor, s = ed.selection, b = s.getBookmark(); + ed.setContent(ed.getContent()); + s.moveToBookmark(b); + }, + + mceRemoveNode : function(ui, val) { + var ed = this.editor, s = ed.selection, b, n = val || s.getNode(); + + // Make sure that the body node isn't removed + if (n == ed.getBody()) + return; + + b = s.getBookmark(); + ed.dom.remove(n, 1); + s.moveToBookmark(b); + ed.nodeChanged(); + }, + + mceSelectNodeDepth : function(ui, val) { + var ed = this.editor, s = ed.selection, c = 0; + + ed.dom.getParent(s.getNode(), function(n) { + if (n.nodeType == 1 && c++ == val) { + s.select(n); + ed.nodeChanged(); + return false; + } + }, ed.getBody()); + }, + + mceSelectNode : function(u, v) { + this.editor.selection.select(v); + }, + + mceInsertContent : function(ui, val) { + this.editor.selection.setContent(val); + }, + + mceInsertRawHTML : function(ui, val) { + var ed = this.editor; + + ed.selection.setContent('tiny_mce_marker'); + ed.setContent(ed.getContent().replace(/tiny_mce_marker/g, val)); + }, + + mceRepaint : function() { + var s, b, e = this.editor; + + if (tinymce.isGecko) { + try { + s = e.selection; + b = s.getBookmark(true); + + if (s.getSel()) + s.getSel().selectAllChildren(e.getBody()); + + s.collapse(true); + s.moveToBookmark(b); + } catch (ex) { + // Ignore + } + } + }, + + queryStateUnderline : function() { + var ed = this.editor, n = ed.selection.getNode(); + + if (n && n.nodeName == 'A') + return false; + + return this._queryState('Underline'); + }, + + queryStateOutdent : function() { + var ed = this.editor, n; + + if (ed.settings.inline_styles) { + if ((n = ed.dom.getParent(ed.selection.getStart(), ed.dom.isBlock)) && parseInt(n.style.paddingLeft) > 0) + return true; + + if ((n = ed.dom.getParent(ed.selection.getEnd(), ed.dom.isBlock)) && parseInt(n.style.paddingLeft) > 0) + return true; + } + + return this.queryStateInsertUnorderedList() || this.queryStateInsertOrderedList() || (!ed.settings.inline_styles && !!ed.dom.getParent(ed.selection.getNode(), 'BLOCKQUOTE')); + }, + + queryStateInsertUnorderedList : function() { + return this.editor.dom.getParent(this.editor.selection.getNode(), 'UL'); + }, + + queryStateInsertOrderedList : function() { + return this.editor.dom.getParent(this.editor.selection.getNode(), 'OL'); + }, + + queryStatemceBlockQuote : function() { + return !!this.editor.dom.getParent(this.editor.selection.getStart(), function(n) {return n.nodeName === 'BLOCKQUOTE';}); + }, + + _applyInlineStyle : function(na, at, op) { + var t = this, ed = t.editor, dom = ed.dom, bm, lo = {}, kh, found; + + na = na.toUpperCase(); + + if (op && op.check_classes && at['class']) + op.check_classes.push(at['class']); + + function removeEmpty() { + each(dom.select(na).reverse(), function(n) { + var c = 0; + + // Check if there is any attributes + each(dom.getAttribs(n), function(an) { + if (an.nodeName.substring(0, 1) != '_' && dom.getAttrib(n, an.nodeName) != '') { + //console.log(dom.getOuterHTML(n), dom.getAttrib(n, an.nodeName)); + c++; + } + }); + + // No attributes then remove the element and keep the children + if (c == 0) + dom.remove(n, 1); + }); + }; + + function replaceFonts() { + var bm; + + each(dom.select('span,font'), function(n) { + if (n.style.fontFamily == 'mceinline' || n.face == 'mceinline') { + if (!bm) + bm = ed.selection.getBookmark(); + + at._mce_new = '1'; + dom.replace(dom.create(na, at), n, 1); + } + }); + + // Remove redundant elements + each(dom.select(na + '[_mce_new]'), function(n) { + function removeStyle(n) { + if (n.nodeType == 1) { + each(at.style, function(v, k) { + dom.setStyle(n, k, ''); + }); + + // Remove spans with the same class or marked classes + if (at['class'] && n.className && op) { + each(op.check_classes, function(c) { + if (dom.hasClass(n, c)) + dom.removeClass(n, c); + }); + } + } + }; + + // Remove specified style information from child elements + each(dom.select(na, n), removeStyle); + + // Remove the specified style information on parent if current node is only child (IE) + if (n.parentNode && n.parentNode.nodeType == 1 && n.parentNode.childNodes.length == 1) + removeStyle(n.parentNode); + + // Remove the child elements style info if a parent already has it + dom.getParent(n.parentNode, function(pn) { + if (pn.nodeType == 1) { + if (at.style) { + each(at.style, function(v, k) { + var sv; + + if (!lo[k] && (sv = dom.getStyle(pn, k))) { + if (sv === v) + dom.setStyle(n, k, ''); + + lo[k] = 1; + } + }); + } + + // Remove spans with the same class or marked classes + if (at['class'] && pn.className && op) { + each(op.check_classes, function(c) { + if (dom.hasClass(pn, c)) + dom.removeClass(n, c); + }); + } + } + + return false; + }); + + n.removeAttribute('_mce_new'); + }); + + removeEmpty(); + ed.selection.moveToBookmark(bm); + + return !!bm; + }; + + // Create inline elements + ed.focus(); + ed.getDoc().execCommand('FontName', false, 'mceinline'); + replaceFonts(); + + if (kh = t._applyInlineStyle.keyhandler) { + ed.onKeyUp.remove(kh); + ed.onKeyPress.remove(kh); + ed.onKeyDown.remove(kh); + ed.onSetContent.remove(t._applyInlineStyle.chandler); + } + + if (ed.selection.isCollapsed()) { + // IE will format the current word so this code can't be executed on that browser + if (!isIE) { + each(dom.getParents(ed.selection.getNode(), 'span'), function(n) { + each(at.style, function(v, k) { + var kv; + + if (kv = dom.getStyle(n, k)) { + if (kv == v) { + dom.setStyle(n, k, ''); + found = 2; + return false; + } + + found = 1; + return false; + } + }); + + if (found) + return false; + }); + + if (found == 2) { + bm = ed.selection.getBookmark(); + + removeEmpty(); + + ed.selection.moveToBookmark(bm); + + // Node change needs to be detached since the onselect event + // for the select box will run the onclick handler after onselect call. Todo: Add a nicer fix! + window.setTimeout(function() { + ed.nodeChanged(); + }, 1); + + return; + } + } + + // Start collecting styles + t._pendingStyles = tinymce.extend(t._pendingStyles || {}, at.style); + + t._applyInlineStyle.chandler = ed.onSetContent.add(function() { + delete t._pendingStyles; + }); + + t._applyInlineStyle.keyhandler = kh = function(e) { + // Use pending styles + if (t._pendingStyles) { + at.style = t._pendingStyles; + delete t._pendingStyles; + } + + if (replaceFonts()) { + ed.onKeyDown.remove(t._applyInlineStyle.keyhandler); + ed.onKeyPress.remove(t._applyInlineStyle.keyhandler); + } + + if (e.type == 'keyup') + ed.onKeyUp.remove(t._applyInlineStyle.keyhandler); + }; + + ed.onKeyDown.add(kh); + ed.onKeyPress.add(kh); + ed.onKeyUp.add(kh); + } else + t._pendingStyles = 0; + } + }); +})(tinymce); \ No newline at end of file diff --git a/js/tiny_mce/classes/EditorManager.js b/js/tiny_mce/classes/EditorManager.js new file mode 100644 index 0000000000..69d8970449 --- /dev/null +++ b/js/tiny_mce/classes/EditorManager.js @@ -0,0 +1,453 @@ +/** + * $Id: EditorManager.js 1160 2009-06-18 18:54:44Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(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; + + /**#@+ + * @class This class is used to create multiple editor instances and contain them in a collection. So it's both a factory and a manager for editor instances. + * @static + * @member tinymce.EditorManager + */ + tinymce.create('static tinymce.EditorManager', { + editors : {}, + i18n : {}, + activeEditor : null, + + /**#@+ + * @method + */ + + /** + * Preinitializes the EditorManager class. This method will be called automatically when the page loads and it + * will setup some important paths and URIs and attach some document events. + */ + preInit : function() { + var t = this, lo = window.location; + + // Setup some URLs where the editor API is located and where the document is + tinymce.documentBaseURL = lo.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); + if (!/[\/\\]$/.test(tinymce.documentBaseURL)) + tinymce.documentBaseURL += '/'; + + tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); + tinymce.EditorManager.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 + t.onBeforeUnload = new tinymce.util.Dispatcher(t); + + // Must be on window or IE will leak if the editor is placed in frame or iframe + Event.add(window, 'beforeunload', function(e) { + t.onBeforeUnload.dispatch(t, e); + }); + }, + + /** + * Initializes a set of editors. This method will create a bunch of editors based in the input. + * + * @param {Object} s Settings object to be passed to each editor instance. + */ + init : function(s) { + var t = this, pl, sl = tinymce.ScriptLoader, c, 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", + strict_loading_mode : document.contentType == 'application/xhtml+xml' + }, s); + + t.settings = s; + + // If page not loaded and strict mode isn't enabled then load them + if (!Event.domLoaded && !s.strict_loading_mode) { + // Load language + if (s.language) + sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); + + // Load theme + if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) + ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); + + // Load plugins + if (s.plugins) { + pl = explode(s.plugins); + + // Load compat2x first + if (tinymce.inArray(pl, 'compat2x') != -1) + PluginManager.load('compat2x', 'plugins/compat2x/editor_plugin' + tinymce.suffix + '.js'); + + // Load rest if plugins + each(pl, function(v) { + if (v && v.charAt(0) != '-' && !PluginManager.urls[v]) { + // Skip safari plugin for other browsers + if (!tinymce.isWebKit && v == 'safari') + return; + + PluginManager.load(v, 'plugins/' + v + '/editor_plugin' + tinymce.suffix + '.js'); + } + }); + } + + sl.loadQueue(); + } + + // Legacy call + Event.add(document, 'init', function() { + var l, co; + + execCallback(s, 'onpageload'); + + // Verify that it's a valid browser + if (s.browsers) { + l = false; + + each(explode(s.browsers), function(v) { + switch (v) { + case 'ie': + case 'msie': + if (tinymce.isIE) + l = true; + break; + + case 'gecko': + if (tinymce.isGecko) + l = true; + break; + + case 'safari': + case 'webkit': + if (tinymce.isWebKit) + l = true; + break; + + case 'opera': + if (tinymce.isOpera) + l = true; + + break; + } + }); + + // Not a valid one + if (!l) + return; + } + + 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 { + c = 0; + + each(document.forms, function(f) { + each(f.elements, function(e) { + if (e.name === v) { + v = 'mce_editor_' + c; + 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. + * + * @param {String} id Editor instance id to return. + * @return {tinymce.Editor} Editor instance to return. + */ + get : function(id) { + return this.editors[id]; + }, + + /** + * Returns a editor instance by id. This method was added for compatibility with the 2.x branch. + * + * @param {String} id Editor instance id to return. + * @return {tinymce.Editor} Editor instance to return. + */ + getInstanceById : function(id) { + return this.get(id); + }, + + /** + * Adds an editor instance to the editor collection. This will also set it as the active editor. + * + * @param {tinymce.Editor} e Editor instance to add to the collection. + * @return {tinymce.Editor} The same instance that got passed in. + */ + add : function(e) { + this.editors[e.id] = e; + this._setActive(e); + + return e; + }, + + /** + * Removes a editor instance from the collection. + * + * @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(e) { + var t = this; + + // Not in the collection + if (!t.editors[e.id]) + return null; + + delete t.editors[e.id]; + + // Select another editor since the active one was removed + if (t.activeEditor == e) { + t._setActive(null); + + each(t.editors, function(e) { + t._setActive(e); + return false; // Break + }); + } + + e.destroy(); + + return e; + }, + + /** + * Executes a specific command on the currently active editor. + * + * @param {String} c Command to perform for example Bold. + * @param {bool} 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 {bool} 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. + * + * @param {String} id Editor id to perform the command on. + * @param {String} c Command to perform for example Bold. + * @param {bool} 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 {bool} 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. + */ + triggerSave : function() { + each(this.editors, function(e) { + e.save(); + }); + }, + + /** + * Adds a language pack, this gets called by the loaded language files like en.js. + * + * @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(e) { + this.selectedInstance = this.activeEditor = e; + } + + /**#@-*/ + }); + + tinymce.EditorManager.preInit(); +})(tinymce); + +// Short for editor manager window.tinyMCE is needed when TinyMCE gets loaded though a XHR call +var tinyMCE = window.tinyMCE = tinymce.EditorManager; diff --git a/js/tiny_mce/classes/ForceBlocks.js b/js/tiny_mce/classes/ForceBlocks.js new file mode 100644 index 0000000000..c0a4969781 --- /dev/null +++ b/js/tiny_mce/classes/ForceBlocks.js @@ -0,0 +1,644 @@ +/** + * $Id: ForceBlocks.js 1137 2009-05-22 15:13:40Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + // Shorten names + var Event, isIE, isGecko, isOpera, each, extend; + + Event = tinymce.dom.Event; + isIE = tinymce.isIE; + isGecko = tinymce.isGecko; + isOpera = tinymce.isOpera; + each = tinymce.each; + extend = tinymce.extend; + + 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(/[ \t\r\n]+/g, '') == ''; + }; + + /** + * 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, '' + elm + '>'); + + o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0' + elm + '>'); + + if (!isIE && !isOpera && o.set) { + // Use instead of BR in padded paragraphs + o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2>' + elm + '>'); + o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2>' + elm + '>'); + } else + o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0' + elm + '>'); + }; + + 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; + + // Force root blocks when typing and when getting output + if (s.forced_root_block) { + 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, s = ed.selection; + + if (e.keyCode == 13 && s.getNode().nodeName != 'LI') { + s.setContent(' ', {format : 'raw'}); + n = ed.dom.get('__'); + n.removeAttribute('id'); + s.select(n); + s.collapse(); + return Event.cancel(e); + } + }); + } + + return; + } + + if (!isIE && s.force_p_newlines) { +/* ed.onPreProcess.add(function(ed, o) { + each(ed.dom.select('br', o.node), function(n) { + var p = n.parentNode; + + // Replace with + if (p && p.nodeName == 'p' && (p.childNodes.length == 1 || p.lastChild == n)) { + p.replaceChild(ed.getDoc().createTextNode('\u00a0'), n); + } + }); + });*/ + + ed.onKeyPress.add(function(ed, e) { + if (e.keyCode == 13 && !e.shiftKey) { + if (!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); + }); + } + } + + function ren(rn, na) { + var ne = ed.dom.create(na); + + each(rn.attributes, function(a) { + if (a.specified && a.nodeValue) + ne.setAttribute(a.nodeName.toLowerCase(), a.nodeValue); + }); + + each(rn.childNodes, function(n) { + ne.appendChild(n.cloneNode(true)); + }); + + rn.parentNode.replaceChild(ne, rn); + + return ne; + }; + + // Padd empty inline elements within block elements + // For example: becomes + ed.onPreProcess.add(function(ed, o) { + each(ed.dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) { + if (isEmpty(p)) { + each(ed.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 = ed.selection.getNode().nodeName; + }); + + ed.onKeyUp.add(function(ed, e) { + var bl, sel = ed.selection, n = sel.getNode(), b = ed.getBody(); + + if (b.childNodes.length === 1 && n.nodeName == 'P') { + n = ren(n, s.element); + sel.select(n); + sel.collapse(); + ed.nodeChanged(); + } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') { + bl = ed.dom.getParent(n, 'p'); + + if (bl) { + ren(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]; + + // Is text or non block element + if (nx.nodeType == 3 || (!t.dom.isBlock(nx) && nx.nodeType != 8)) { + 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; + } + } + + bl = ed.dom.create(ed.settings.forced_root_block); + bl.appendChild(nx.cloneNode(1)); + nx.parentNode.replaceChild(bl, 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 (t.dom.getParent(sb, 'ol,ul,pre')) + 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) && sn.nodeValue && so == sn.nodeValue.length) + 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(), 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); diff --git a/js/tiny_mce/classes/Popup.js b/js/tiny_mce/classes/Popup.js new file mode 100644 index 0000000000..71662ece23 --- /dev/null +++ b/js/tiny_mce/classes/Popup.js @@ -0,0 +1,412 @@ +/** + * $Id: Popup.js 1150 2009-06-01 11:50:46Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +// Some global instances +var tinymce = null, tinyMCEPopup, tinyMCE; + +/**#@+ + * @class 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 + * @member tinyMCEPopup + */ +tinyMCEPopup = { + /**#@+ + * @method + */ + + /** + * Initializes the popup this will be called automatically. + */ + 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. + * + * @return {Window} Reference to the parent window that opened the dialog. + */ + getWin : function() { + return window.dialogArguments || opener || parent || top; + }, + + /** + * Returns a window argument/parameter by name. + * + * @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. + * + * @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. + * + * @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. + * + * @param {String} cmd Command to execute. + * @param {bool} 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. + */ + resizeToInnerSize : function() { + var t = this, n, b = document.body, vp = t.dom.getViewPort(window), dw, dh; + + dw = t.getWindowArg('mce_width') - vp.w; + dh = t.getWindowArg('mce_height') - vp.h; + + if (t.isWindow) + window.resizeBy(dw, dh); + else + t.editor.windowManager.resizeBy(dw, dh, t.id); + }, + + /** + * Will executed the specified string when the page has been loaded. This function + * was added for compatibility with the 2.x branch. + * + * @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. + */ + 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. + */ + 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. + */ + 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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + */ + 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 diff --git a/js/tiny_mce/classes/UndoManager.js b/js/tiny_mce/classes/UndoManager.js new file mode 100644 index 0000000000..2a94c7a0ae --- /dev/null +++ b/js/tiny_mce/classes/UndoManager.js @@ -0,0 +1,183 @@ +/** + * $Id: UndoManager.js 1045 2009-03-04 20:03:18Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + /**#@+ + * @class 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. + * @member tinymce.UndoManager + */ + tinymce.create('tinymce.UndoManager', { + index : 0, + data : null, + typing : 0, + + /** + * Constructs a new UndoManager instance. + * + * @constructor + * @param {tinymce.Editor} ed Editor instance to undo/redo in. + */ + UndoManager : function(ed) { + var t = this, Dispatcher = tinymce.util.Dispatcher; + + t.editor = ed; + t.data = []; + t.onAdd = new Dispatcher(this); + t.onUndo = new Dispatcher(this); + t.onRedo = new Dispatcher(this); + }, + + /**#@+ + * @method + */ + + /** + * Adds a new undo level/snapshot to the undo list. + * + * @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(l) { + var t = this, i, ed = t.editor, b, s = ed.settings, la; + + l = l || {}; + l.content = l.content || ed.getContent({format : 'raw', no_events : 1}); + + // Add undo level if needed + l.content = l.content.replace(/^\s*|\s*$/g, ''); + la = t.data[t.index > 0 && (t.index == 0 || t.index == t.data.length) ? t.index - 1 : t.index]; + if (!l.initial && la && l.content == la.content) + return null; + + // Time to compress + if (s.custom_undo_redo_levels) { + if (t.data.length > s.custom_undo_redo_levels) { + for (i = 0; i < t.data.length - 1; i++) + t.data[i] = t.data[i + 1]; + + t.data.length--; + t.index = t.data.length; + } + } + + if (s.custom_undo_redo_restore_selection && !l.initial) + l.bookmark = b = l.bookmark || ed.selection.getBookmark(); + + if (t.index < t.data.length) + t.index++; + + // Only initial marked undo levels should be allowed as first item + // This to workaround a bug with Firefox and the blur event + if (t.data.length === 0 && !l.initial) + return null; + + // Add level + t.data.length = t.index + 1; + t.data[t.index++] = l; + + if (l.initial) + t.index = 0; + + // Set initial bookmark use first real undo level + if (t.data.length == 2 && t.data[0].initial) + t.data[0].bookmark = b; + + t.onAdd.dispatch(t, l); + ed.isNotDirty = 0; + + //console.dir(t.data); + + return l; + }, + + /** + * Undoes the last action. + * + * @return {Object} Undo level or null if no undo was performed. + */ + undo : function() { + var t = this, ed = t.editor, l = l, i; + + if (t.typing) { + t.add(); + t.typing = 0; + } + + if (t.index > 0) { + // If undo on last index then take snapshot + if (t.index == t.data.length && t.index > 1) { + i = t.index; + t.typing = 0; + + if (!t.add()) + t.index = i; + + --t.index; + } + + l = t.data[--t.index]; + ed.setContent(l.content, {format : 'raw'}); + ed.selection.moveToBookmark(l.bookmark); + + t.onUndo.dispatch(t, l); + } + + return l; + }, + + /** + * Redoes the last action. + * + * @return {Object} Redo level or null if no redo was performed. + */ + redo : function() { + var t = this, ed = t.editor, l = null; + + if (t.index < t.data.length - 1) { + l = t.data[++t.index]; + ed.setContent(l.content, {format : 'raw'}); + ed.selection.moveToBookmark(l.bookmark); + + t.onRedo.dispatch(t, l); + } + + return l; + }, + + /** + * Removes all undo levels. + */ + clear : function() { + var t = this; + + t.data = []; + t.index = 0; + t.typing = 0; + t.add({initial : true}); + }, + + /** + * Returns true/false if the undo manager has any undo levels. + * + * @return {bool} true/false if the undo manager has any undo levels. + */ + hasUndo : function() { + return this.index != 0 || this.typing; + }, + + /** + * Returns true/false if the undo manager has any redo levels. + * + * @return {bool} true/false if the undo manager has any redo levels. + */ + hasRedo : function() { + return this.index < this.data.length - 1; + } + + /**#@-*/ + }); +})(tinymce); diff --git a/js/tiny_mce/classes/WindowManager.js b/js/tiny_mce/classes/WindowManager.js new file mode 100644 index 0000000000..eed80232e5 --- /dev/null +++ b/js/tiny_mce/classes/WindowManager.js @@ -0,0 +1,169 @@ +/** + * $Id: WindowManager.js 1045 2009-03-04 20:03:18Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; + + /**#@+ + * @class This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs. + * @member tinymce.WindowManager + */ + tinymce.create('tinymce.WindowManager', { + /** + * Constructs a new window manager instance. + * + * @constructor + * @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 = {}; + }, + + /**#@+ + * @method + */ + + /** + * Opens a new window. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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); + }, + + // 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 new file mode 100644 index 0000000000..e5498bb081 --- /dev/null +++ b/js/tiny_mce/classes/adapter/jquery/adapter.js @@ -0,0 +1,240 @@ +/** + * $Id: adapter.js 1167 2009-06-29 13:07:20Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + * + * This file contains all adapter logic needed to use jQuery as the base API for TinyMCE. + */ + +// #ifdef jquery_adapter + +(function($, tinymce) { + var is = tinymce.is; + + if (!window.jQuery) + return alert("Load jQuery first!"); + + // 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 || []);}, + 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]); + }; +})(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 new file mode 100644 index 0000000000..c315137651 --- /dev/null +++ b/js/tiny_mce/classes/adapter/jquery/jquery.tinymce.js @@ -0,0 +1,179 @@ +/** + * $Id: jquery.uri.js 453 2008-10-14 12:24:41Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function($) { + var lazyLoading, delayedInits = []; + + function patch(type, name, patch_func) { + var func; + + func = $.fn[name]; + + $.fn[name] = function() { + var val; + + if (type !== 'after') { + val = patch_func.apply(this, arguments); + + // We got a return value pass out that instead + if (val !== undefined) + return val; + } + + val = func.apply(this, arguments); + + if (type !== 'before') + patch_func.apply(this, arguments); + + return val; + }; + }; + + $.fn.tinymce = function(settings) { + var t = this, url, suffix = '', ed; + + // No match then just ignore the call + if (!t.length) + return; + + // Get editor instance + if (!settings) + return tinyMCE.get(this[0].id); + + function init() { + // Apply patches once + if (applyPatch) { + applyPatch(); + applyPatch = null; + } + + // Create an editor instance for each matched node + t.each(function(i, n) { + var ed, id = n.id || tinymce.DOM.uniqueId(); + + n.id = id; + ed = new tinymce.Editor(id, settings); + + ed.render(); + }); + }; + + // Load TinyMCE on demand + if (!window['tinymce'] && !lazyLoading && (url = settings.script_url)) { + lazyLoading = 1; + + if (/_(src|dev)\.js/g.test(url)) + suffix = '_src'; + + window.tinyMCEPreInit = { + base : url.substring(0, url.lastIndexOf('/')), + suffix : suffix, + query : '' + }; + + $.getScript(url, function() { + // Script is loaded time to initialize TinyMCE + tinymce.dom.Event.domLoaded = 1; + lazyLoading = 2; + init(); + + $.each(delayedInits, function(i, init) { + init(); + }); + }); + } else { + if (lazyLoading === 1) + delayedInits.push(init); + else + init(); + } + }; + + // Add :tinymce psuedo selector + $.extend($.expr[':'], { + tinymce : function(e) { + return e.id && !!tinyMCE.get(e.id); + } + }); + + function applyPatch() { + function removeEditors() { + this.find('span.mceEditor,div.mceEditor').each(function(i, n) { + var ed; + + if (ed = tinyMCE.get(n.id.replace(/_parent$/, ''))) { + ed.remove(); + } + }); + }; + + function loadOrSave(value) { + var ed; + + // Handle set value + if (value !== undefined) { + removeEditors.call(this); + + // Saves the contents before get/set value of textarea/div + this.each(function(i, node) { + var ed; + + if (ed = tinyMCE.get(node.id)) + ed.setContent(value); + }); + } else if (this.length > 0) { + // Handle get value + if (ed = tinyMCE.get(this[0].id)) + return ed.getContent(); + } + }; + + // Patch various jQuery functions + patch("both", 'text', function(value) { + // Text encode value + if (value !== undefined) + return loadOrSave.call(this, value); + + // Get contents as plain text + if (this.length > 0) { + // Handle get value + if (ed = tinyMCE.get(this[0].id)) + return ed.getContent().replace(/<[^>]+>/g, ''); + } + }); + + $.each(['val', 'html'], function(i, name) { + patch("both", name, loadOrSave); + }); + + $.each(['append', 'prepend'], function(i, name) { + patch("before", name, function(value) { + if (value !== undefined) { + this.each(function(i, node) { + var ed; + + if (ed = tinyMCE.get(node.id)) { + if (name === 'append') + ed.setContent(ed.getContent() + value); + else + ed.setContent(value + ed.getContent()); + } + }); + } + }); + }); + + patch("both", 'attr', function(name, value) { + if (name && name === 'value') + return loadOrSave.call(this, value); + }); + + $.each(['remove', 'replaceWith', 'replaceAll', 'empty'], function(i, name) { + patch("before", name, removeEditors); + }); + }; +})(jQuery); \ No newline at end of file diff --git a/js/tiny_mce/classes/adapter/prototype/adapter.js b/js/tiny_mce/classes/adapter/prototype/adapter.js new file mode 100644 index 0000000000..51822dfaf8 --- /dev/null +++ b/js/tiny_mce/classes/adapter/prototype/adapter.js @@ -0,0 +1,38 @@ +/** + * $Id: adapter.js 1000 2009-02-12 13:03:40Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + * + * This file contains all adapter logic needed to use prototype library as the base API for TinyMCE. + */ + +// #ifdef prototype_adapter + +(function() { + if (!window.Prototype) + return alert("Load prototype first!"); + + // Patch in core NS functions + tinymce.extend(tinymce, { + trim : function(s) {return s ? s.strip() : '';}, + inArray : function(a, v) {return a && a.indexOf ? a.indexOf(v) : -1;} + }); + + // Patch in functions in various clases + // Add a "#ifndefjquery" statement around each core API function you add below + var patches = { + 'tinymce.util.JSON' : { + serialize : function(o) { + return o.toJSON(); + } + }, + }; + + // Patch functions after a class is created + tinymce.onCreate = function(ty, c, p) { + tinymce.extend(p, patches[c]); + }; +})(); + +// #endif diff --git a/js/tiny_mce/classes/commands/BlockQuote.js b/js/tiny_mce/classes/commands/BlockQuote.js new file mode 100644 index 0000000000..44ef9b1243 --- /dev/null +++ b/js/tiny_mce/classes/commands/BlockQuote.js @@ -0,0 +1,135 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + tinymce.GlobalCommands.add('mceBlockQuote', function() { + var ed = this, s = ed.selection, dom = ed.dom, sb, eb, n, bm, bq, r, bq2, i, nl; + + function getBQ(e) { + return dom.getParent(e, function(n) {return n.nodeName === 'BLOCKQUOTE';}); + }; + + // Get start/end block + sb = dom.getParent(s.getStart(), dom.isBlock); + eb = dom.getParent(s.getEnd(), dom.isBlock); + + // Remove blockquote(s) + if (bq = getBQ(sb)) { + if (sb != eb || sb.childNodes.length > 1 || (sb.childNodes.length == 1 && sb.firstChild.nodeName != 'BR')) + bm = s.getBookmark(); + + // Move all elements after the end block into new bq + if (getBQ(eb)) { + bq2 = bq.cloneNode(false); + + while (n = eb.nextSibling) + bq2.appendChild(n.parentNode.removeChild(n)); + } + + // Add new bq after + if (bq2) + dom.insertAfter(bq2, bq); + + // Move all selected blocks after the current bq + nl = s.getSelectedBlocks(sb, eb); + for (i = nl.length - 1; i >= 0; i--) { + dom.insertAfter(nl[i], bq); + } + + // Empty bq, then remove it + if (/^\s*$/.test(bq.innerHTML)) + dom.remove(bq, 1); // Keep children so boomark restoration works correctly + + // Empty bq, then remote it + if (bq2 && /^\s*$/.test(bq2.innerHTML)) + dom.remove(bq2, 1); // Keep children so boomark restoration works correctly + + if (!bm) { + // Move caret inside empty block element + if (!tinymce.isIE) { + r = ed.getDoc().createRange(); + r.setStart(sb, 0); + r.setEnd(sb, 0); + s.setRng(r); + } else { + s.select(sb); + s.collapse(0); + + // IE misses the empty block some times element so we must move back the caret + if (dom.getParent(s.getStart(), dom.isBlock) != sb) { + r = s.getRng(); + r.move('character', -1); + r.select(); + } + } + } else + ed.selection.moveToBookmark(bm); + + return; + } + + // Since IE can start with a totally empty document we need to add the first bq and paragraph + if (tinymce.isIE && !sb && !eb) { + ed.getDoc().execCommand('Indent'); + n = getBQ(s.getNode()); + n.style.margin = n.dir = ''; // IE adds margin and dir to bq + return; + } + + if (!sb || !eb) + return; + + // If empty paragraph node then do not use bookmark + if (sb != eb || sb.childNodes.length > 1 || (sb.childNodes.length == 1 && sb.firstChild.nodeName != 'BR')) + bm = s.getBookmark(); + + // Move selected block elements into a bq + tinymce.each(s.getSelectedBlocks(getBQ(s.getStart()), getBQ(s.getEnd())), function(e) { + // Found existing BQ add to this one + if (e.nodeName == 'BLOCKQUOTE' && !bq) { + bq = e; + return; + } + + // No BQ found, create one + if (!bq) { + bq = dom.create('blockquote'); + e.parentNode.insertBefore(bq, e); + } + + // Add children from existing BQ + if (e.nodeName == 'BLOCKQUOTE' && bq) { + n = e.firstChild; + + while (n) { + bq.appendChild(n.cloneNode(true)); + n = n.nextSibling; + } + + dom.remove(e); + return; + } + + // Add non BQ element to BQ + bq.appendChild(dom.remove(e)); + }); + + if (!bm) { + // Move caret inside empty block element + if (!tinymce.isIE) { + r = ed.getDoc().createRange(); + r.setStart(sb, 0); + r.setEnd(sb, 0); + s.setRng(r); + } else { + s.select(sb); + s.collapse(1); + } + } else + s.moveToBookmark(bm); + }); +})(tinymce); diff --git a/js/tiny_mce/classes/commands/CutCopyPaste.js b/js/tiny_mce/classes/commands/CutCopyPaste.js new file mode 100644 index 0000000000..886842ddc1 --- /dev/null +++ b/js/tiny_mce/classes/commands/CutCopyPaste.js @@ -0,0 +1,24 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + tinymce.each(['Cut', 'Copy', 'Paste'], function(cmd) { + tinymce.GlobalCommands.add(cmd, function() { + var ed = this, doc = ed.getDoc(); + + try { + doc.execCommand(cmd, false, null); + + // On WebKit the command will just be ignored if it's not enabled + if (!doc.queryCommandSupported(cmd)) + throw 'Error'; + } catch (ex) { + ed.windowManager.alert(ed.getLang('clipboard_no_support')); + } + }); + }); +})(tinymce); diff --git a/js/tiny_mce/classes/commands/InsertHorizontalRule.js b/js/tiny_mce/classes/commands/InsertHorizontalRule.js new file mode 100644 index 0000000000..02542aa85f --- /dev/null +++ b/js/tiny_mce/classes/commands/InsertHorizontalRule.js @@ -0,0 +1,15 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + tinymce.GlobalCommands.add('InsertHorizontalRule', function() { + if (tinymce.isOpera) + return this.getDoc().execCommand('InsertHorizontalRule', false, ''); + + this.selection.setContent(''); + }); +})(tinymce); diff --git a/js/tiny_mce/classes/commands/RemoveFormat.js b/js/tiny_mce/classes/commands/RemoveFormat.js new file mode 100644 index 0000000000..6a43902f5c --- /dev/null +++ b/js/tiny_mce/classes/commands/RemoveFormat.js @@ -0,0 +1,173 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + function processRange(dom, start, end, callback) { + var ancestor, n, startPoint, endPoint, sib; + + function findEndPoint(n, c) { + do { + if (n.parentNode == c) + return n; + + n = n.parentNode; + } while(n); + }; + + function process(n) { + callback(n); + tinymce.walk(n, callback, 'childNodes'); + }; + + // Find common ancestor and end points + ancestor = dom.findCommonAncestor(start, end); + startPoint = findEndPoint(start, ancestor) || start; + endPoint = findEndPoint(end, ancestor) || end; + + // Process left leaf + for (n = start; n && n != startPoint; n = n.parentNode) { + for (sib = n.nextSibling; sib; sib = sib.nextSibling) + process(sib); + } + + // Process middle from start to end point + if (startPoint != endPoint) { + for (n = startPoint.nextSibling; n && n != endPoint; n = n.nextSibling) + process(n); + } else + process(startPoint); + + // Process right leaf + for (n = end; n && n != endPoint; n = n.parentNode) { + for (sib = n.previousSibling; sib; sib = sib.previousSibling) + process(sib); + } + }; + + tinymce.GlobalCommands.add('RemoveFormat', function() { + var ed = this, dom = ed.dom, s = ed.selection, r = s.getRng(1), nodes = [], bm, start, end, sc, so, ec, eo, n; + + function findFormatRoot(n) { + var sp; + + dom.getParent(n, function(n) { + if (dom.is(n, ed.getParam('removeformat_selector'))) + sp = n; + + return dom.isBlock(n); + }, ed.getBody()); + + return sp; + }; + + function collect(n) { + if (dom.is(n, ed.getParam('removeformat_selector'))) + nodes.push(n); + }; + + function walk(n) { + collect(n); + tinymce.walk(n, collect, 'childNodes'); + }; + + bm = s.getBookmark(); + sc = r.startContainer; + ec = r.endContainer; + so = r.startOffset; + eo = r.endOffset; + sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc; + ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec; + + // Same container + if (sc == ec) { // TEXT_NODE + start = findFormatRoot(sc); + + // Handle single text node + if (sc.nodeType == 3) { + if (start && start.nodeType == 1) { // ELEMENT + n = sc.splitText(so); + n.splitText(eo - so); + dom.split(start, n); + + s.moveToBookmark(bm); + } + + return; + } + + // Handle single element + walk(dom.split(start, sc) || sc); + } else { + // Find start/end format root + start = findFormatRoot(sc); + end = findFormatRoot(ec); + + // Split start text node + if (start) { + if (sc.nodeType == 3) { // TEXT + // Since IE doesn't support white space nodes in the DOM we need to + // add this invisible character so that the splitText function can split the contents + if (so == sc.nodeValue.length) + sc.nodeValue += '\uFEFF'; // Yet another pesky IE fix + + sc = sc.splitText(so); + } + } + + // Split end text node + if (end) { + if (ec.nodeType == 3) // TEXT + ec.splitText(eo); + } + + // If the start and end format root is the same then we need to wrap + // the end node in a span since the split calls might change the reference + // Example: x[yz---12]3 + if (start && start == end) + dom.replace(dom.create('span', {id : '__end'}, ec.cloneNode(true)), ec); + + // Split all start containers down to the format root + if (start) + start = dom.split(start, sc); + else + start = sc; + + // If there is a span wrapper use that one instead + if (n = dom.get('__end')) { + ec = n; + end = findFormatRoot(ec); + } + + // Split all end containers down to the format root + if (end) + end = dom.split(end, ec); + else + end = ec; + + // Collect nodes in between + processRange(dom, start, end, collect); + + // Remove invisible character for IE workaround if we find it + if (sc.nodeValue == '\uFEFF') + sc.nodeValue = ''; + + // Process start/end container elements + walk(ec); + walk(sc); + } + + // Remove all collected nodes + tinymce.each(nodes, function(n) { + dom.remove(n, 1); + }); + + // Remove leftover wrapper + dom.remove('__end', 1); + + s.moveToBookmark(bm); + }); +})(tinymce); diff --git a/js/tiny_mce/classes/commands/UndoRedo.js b/js/tiny_mce/classes/commands/UndoRedo.js new file mode 100644 index 0000000000..b2745e1e38 --- /dev/null +++ b/js/tiny_mce/classes/commands/UndoRedo.js @@ -0,0 +1,38 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function() { + var cmds = tinymce.GlobalCommands; + + cmds.add(['mceEndUndoLevel', 'mceAddUndoLevel'], function() { + this.undoManager.add(); + }); + + cmds.add('Undo', function() { + var ed = this; + + if (ed.settings.custom_undo_redo) { + ed.undoManager.undo(); + ed.nodeChanged(); + return true; + } + + return false; // Run browser command + }); + + cmds.add('Redo', function() { + var ed = this; + + if (ed.settings.custom_undo_redo) { + ed.undoManager.redo(); + ed.nodeChanged(); + return true; + } + + return false; // Run browser command + }); +})(); diff --git a/js/tiny_mce/classes/dom/DOMUtils.js b/js/tiny_mce/classes/dom/DOMUtils.js new file mode 100644 index 0000000000..8f07a4a297 --- /dev/null +++ b/js/tiny_mce/classes/dom/DOMUtils.js @@ -0,0 +1,1823 @@ +/** + * $Id: DOMUtils.js 1154 2009-06-10 17:31:03Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + // Shorten names + var each = tinymce.each, is = tinymce.is; + var isWebKit = tinymce.isWebKit, isIE = tinymce.isIE; + + /**#@+ + * @class Utility class for various DOM manipulation and retrival functions. + * @member 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 + * @param {Document} d Document reference to bind the utility class to. + * @param {settings} s Optional settings collection. + */ + DOMUtils : function(d, s) { + var t = this; + + 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; + } + } + + tinymce.addUnload(t.destroy, t); + }, + + /**#@+ + * @method + */ + + /** + * 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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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; + }, + + // #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. + * + * @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. + * + * @param {Node/NodeList} n DOM node to match or an array of nodes to match. + * @param {String} patt CSS pattern to match the element agains. + */ + is : function(n, patt) { + return tinymce.dom.Sizzle.matches(patt, n.nodeType ? [n] : n).length > 0; + }, + + // #endif + + /** + * Adds the specified element to another element or elements. + * + * @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 {bool} 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. + * + * @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. + * + * @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 + '' + n + '>'; + + return o + ' />'; + }, + + /** + * Removes/deletes the specified element(s) from the DOM. + * + * @param {String/Element/Array} n ID of element or DOM element object or array containing multiple elements/ids. + * @param {bool} k 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(n, k) { + var t = this; + + return this.run(n, function(n) { + var p, g, i; + + p = n.parentNode; + + if (!p) + return null; + + if (k) { + for (i = n.childNodes.length - 1; i >= 0; i--) + t.insertAfter(n.childNodes[i], n); + + //each(n.childNodes, function(c) { + // p.insertBefore(c.cloneNode(true), n); + //}); + } + + // Fix IE psuedo leak + if (t.fixPsuedoLeaks) { + p = n.cloneNode(true); + k = 'IELeakGarbageBin'; + g = t.get(k) || t.add(t.doc.body, 'div', {id : k, style : 'display:none'}); + g.appendChild(n); + g.innerHTML = ''; + + return p; + } + + return p.removeChild(n); + }); + }, + + /** + * Sets the CSS style value on a HTML element. The name can be a camelcase string + * or the CSS style name like background-color. + * + * @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. + * + * @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). + * + * @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. + * + * @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. + * + * @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. + * + * @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); + + if (n === 'style') { + v = v || e.style.cssText; + + if (v) { + v = t.serializeStyle(t.parseStyle(v)); + + 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. + * + * @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. + * + * @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. + * + * @param {Object} o Object to serialize as string for example: {border : '1px solid red'} + * @return {String} String representation of the style object for example: border: 1px solid red. + */ + serializeStyle : function(o) { + var s = ''; + + each(o, function(v, k) { + if (k && v) { + if (tinymce.isGecko && k.indexOf('-moz-') === 0) + return; + + switch (k) { + case 'color': + case 'background-color': + v = v.toLowerCase(); + break; + } + + s += (s ? ' ' : '') + k + ': ' + v + ';'; + } + }); + + return s; + }, + + /** + * Imports/loads the specified CSS file into the document bound to the class. + * + * @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. + * + * @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. + * + * @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, ' '); + + return e.className = tinymce.trim(v != ' ' ? v : ''); + } + + return e.className; + }); + }, + + /** + * Returns true if the specified element has the specified class. + * + * @param {String/Element} n HTML element or element id string to check CSS class on. + * @param {String] c CSS class to check for. + * @return {bool} 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. + * + * @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. + * + * @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. + * + * @param {String/Element} e Id or element to check display state on. + * @return {bool} 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. + * + * @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. + * + * @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() { + 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 + + // Remove all child nodes + while (e.firstChild) + e.firstChild.removeNode(); + + // 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(/]+)>|/g, ''); + h = h.replace(/<\/p>/g, ''); + + // Set the new HTML with DIVs + set(); + + // Replace all DIV elements with he 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, '<$1b$2>'); + h = h.replace(/<(\/?)em>|]+)>/gi, '<$1i$2>'); + } else if (isIE) { + h = h.replace(/'/g, '''); // IE can't handle apos + h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct + } + + // Fix some issues + h = h.replace(/]+)\/>|/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 (/'); + }; + + if (!tinymce.is(u, 'string')) { + each(u, function(u) { + loadScript(u); + }); + + if (cb) + cb.call(s || t); + } else { + loadScript(u); + + if (cb) + cb.call(s || t); + } + }, + + /** + * Starts the loading of the queue. + * + * @param {function} cb Optional callback to execute when all queued items are loaded. + * @param {Object} s Optional scope to execute the callback in. + */ + loadQueue : function(cb, s) { + var t = this; + + if (!t.queueLoading) { + t.queueLoading = 1; + t.queueCallbacks = []; + + t.loadScripts(t.queue, function() { + t.queueLoading = 0; + + if (cb) + cb.call(s || t); + + each(t.queueCallbacks, function(o) { + o.func.call(o.scope); + }); + }); + } else if (cb) + t.queueCallbacks.push({func : cb, scope : s || t}); + }, + + /** + * Evaluates the specified string inside the global namespace/window scope. + * + * @param {string} Script contents to evaluate. + */ + eval : function(co) { + var w = window; + + // Evaluate script + if (!w.execScript) { + try { + eval.call(w, co); + } catch (ex) { + eval(co, w); // Firefox 3.0a8 + } + } else + w.execScript(co); // IE + }, + + /** + * 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. + * + * @param {Array} sc Array of queue items to load. + * @param {function} cb Optional callback to execute ones all items are loaded. + * @param {Object} s Optional scope to execute callback in. + */ + loadScripts : function(sc, cb, s) { + var t = this, lo = t.lookup; + + function done(o) { + o.state = 2; // Has been loaded + + // Run callback + if (o.func) + o.func.call(o.scope || t); + }; + + function allDone() { + var l; + + // Check if all files are loaded + l = sc.length; + each(sc, function(o) { + o = lo[o.url]; + + if (o.state === 2) {// It has finished loading + done(o); + l--; + } else + load(o); + }); + + // They are all loaded + if (l === 0 && cb) { + cb.call(s || t); + cb = 0; + } + }; + + function load(o) { + if (o.state > 0) + return; + + o.state = 1; // Is loading + + tinymce.dom.ScriptLoader.loadScript(o.url, function() { + done(o); + allDone(); + }); + + /* + tinymce.util.XHR.send({ + url : o.url, + error : t.settings.error, + success : function(co) { + t.eval(co); + done(o); + allDone(); + } + }); + */ + }; + + each(sc, function(o) { + var u = o.url; + + // Add to queue if needed + if (!lo[u]) { + lo[u] = o; + t.queue.push(o); + } else + o = lo[u]; + + // Is already loading or has been loaded + if (o.state > 0) + return; + + if (!Event.domLoaded && !t.settings.strict_mode) { + var ix, ol = ''; + + // Add onload events + if (cb || o.func) { + o.state = 1; // Is loading + + ix = tinymce.dom.ScriptLoader._addOnLoad(function() { + done(o); + allDone(); + }); + + if (tinymce.isIE) + ol = ' onreadystatechange="'; + else + ol = ' onload="'; + + ol += 'tinymce.dom.ScriptLoader._onLoad(this,\'' + u + '\',' + ix + ');"'; + } + + document.write(''); + + if (!o.func) + done(o); + } else + load(o); + }); + + allDone(); + }, + + // Static methods + 'static' : { + _addOnLoad : function(f) { + var t = this; + + t._funcs = t._funcs || []; + t._funcs.push(f); + + return t._funcs.length - 1; + }, + + _onLoad : function(e, u, ix) { + if (!tinymce.isIE || e.readyState == 'complete') + this._funcs[ix].call(this); + }, + + /** + * Loads the specified script without adding it to any load queue. + * + * @param {string} u URL to dynamically load. + * @param {function} cb Callback function to executed on load. + */ + loadScript : function(u, cb) { + var id = tinymce.DOM.uniqueId(), e; + + function done() { + Event.clear(id); + tinymce.DOM.remove(id); + + if (cb) { + cb.call(document, u); + cb = 0; + } + }; + + if (tinymce.isIE) { +/* Event.add(e, 'readystatechange', function(e) { + if (e.target && e.target.readyState == 'complete') + done(); + });*/ + + tinymce.util.XHR.send({ + url : tinymce._addVer(u), + async : false, + success : function(co) { + window.execScript(co); + done(); + } + }); + } else { + e = tinymce.DOM.create('script', {id : id, type : 'text/javascript', src : tinymce._addVer(u)}); + Event.add(e, 'load', done); + + // Check for head or body + (document.getElementsByTagName('head')[0] || document.body).appendChild(e); + } + } + } + + /**#@-*/ + }); + + // 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 new file mode 100644 index 0000000000..87c0a76fda --- /dev/null +++ b/js/tiny_mce/classes/dom/Selection.js @@ -0,0 +1,747 @@ +/** + * $Id: Selection.js 1149 2009-06-01 11:47:08Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + function trimNl(s) { + return s.replace(/[\n\r]+/g, ''); + }; + + // Shorten names + var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; + + /**#@+ + * @class 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. + * @member tinymce.dom.Selection + */ + tinymce.create('tinymce.dom.Selection', { + /** + * Constructs a new selection instance. + * + * @constructor + * @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); + }, + + /**#@+ + * @method + */ + + /** + * Returns the selected contents using the DOM serializer passed in to this class. + * + * @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. + * + * @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 + 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.setEndAfter(c); + t.setRng(r); + + // Delete the marker, and hopefully the caret gets placed in the right location + // Removed this since it seems to remove in FF and simply deleting it + // doesn't seem to affect the caret position in any browser + //d.execCommand('Delete', false, null); + + // 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. + * + * @return {Element} Start element of selection range. + */ + getStart : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(1); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.firstChild; + + return e; + } else { + e = r.startContainer; + + if (e.nodeName == 'BODY') + return e.firstChild; + + return t.dom.getParent(e, '*'); + } + }, + + /** + * Returns the end element of a selection range. If the end is in a text + * node the parent element will be returned. + * + * @return {Element} End element of selection range. + */ + getEnd : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild; + + return e; + } else { + e = r.endContainer; + + if (e.nodeName == 'BODY') + return e.lastChild; + + return t.dom.getParent(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. + * + * @param {bool} si Optional state if the bookmark should be simple or not. Default is complex. + * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection. + */ + getBookmark : function(si) { + var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv; + sx = vp.x; + sy = vp.y; + + // Simple bookmark fast but not as persistent + if (si) + return {rng : r, scrollX : sx, scrollY : sy}; + + // Handle IE + if (isIE) { + // Control selection + if (r.item) { + e = r.item(0); + + each(t.dom.select(e.nodeName), function(n, i) { + if (e == n) { + sp = i; + return false; + } + }); + + return { + tag : e.nodeName, + index : sp, + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + tr = t.dom.doc.body.createTextRange(); + tr.moveToElementText(ro); + tr.collapse(true); + bp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(true); + sp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(false); + le = Math.abs(tr.move('character', c)) - sp; + + return { + start : sp - bp, + length : le, + scrollX : sx, + scrollY : sy + }; + } + + // Handle W3C + e = t.getNode(); + s = t.getSel(); + + if (!s) + return null; + + // Image selection + if (e && e.nodeName == 'IMG') { + return { + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + + function getPos(r, sn, en) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; + + while ((n = w.nextNode()) != null) { + if (n == sn) + d.start = p; + + if (n == en) { + d.end = p; + return d; + } + + p += trimNl(n.nodeValue || '').length; + } + + return null; + }; + + // Caret or selection + if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) { + e = getPos(ro, s.anchorNode, s.focusNode); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + // Count whitespace before + trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + + return { + start : Math.max(e.start + s.anchorOffset - wb, 0), + end : Math.max(e.end + s.focusOffset - wb, 0), + scrollX : sx, + scrollY : sy, + beg : s.anchorOffset - wb == 0 + }; + } else { + e = getPos(ro, r.startContainer, r.endContainer); + + // Count whitespace before start and end container + //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;}); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + return { + start : Math.max(e.start + r.startOffset - wb, 0), + end : Math.max(e.end + r.endOffset - wa, 0), + scrollX : sx, + scrollY : sy, + beg : r.startOffset - wb == 0 + }; + } + }, + + /** + * Restores the selection to the specified bookmark. + * + * @param {Object} bookmark Bookmark to restore selection from. + * @return {bool} true/false if it was successful or not. + */ + moveToBookmark : function(b) { + var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv; + + function getPos(r, sp, ep) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb; + + while ((n = w.nextNode()) != null) { + wa = wb = 0; + + nv = n.nodeValue || ''; + //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;}); + //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;}); + + nvl = trimNl(nv).length; + p += nvl; + + if (p >= sp && !d.startNode) { + o = sp - (p - nvl); + + // Fix for odd quirk in FF + if (b.beg && o >= nvl) + continue; + + d.startNode = n; + d.startOffset = o + wb; + } + + if (p >= ep) { + d.endNode = n; + d.endOffset = ep - (p - nvl) + wb; + return d; + } + } + + return null; + }; + + if (!b) + return false; + + t.win.scrollTo(b.scrollX, b.scrollY); + + // Handle explorer + if (isIE) { + // Handle simple + if (r = b.rng) { + try { + r.select(); + } catch (ex) { + // Ignore + } + + return true; + } + + t.win.focus(); + + // Handle control bookmark + if (b.tag) { + r = ro.createControlRange(); + + each(t.dom.select(b.tag), function(n, i) { + if (i == b.index) + r.addElement(n); + }); + } else { + // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs + try { + // Incorrect bookmark + if (b.start < 0) + return true; + + r = s.createRange(); + r.moveToElementText(ro); + r.collapse(true); + r.moveStart('character', b.start); + r.moveEnd('character', b.length); + } catch (ex2) { + return true; + } + } + + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + + return true; + } + + // Handle W3C + if (!s) + return false; + + // Handle simple + if (b.rng) { + s.removeAllRanges(); + s.addRange(b.rng); + } else { + if (is(b.start) && is(b.end)) { + try { + sd = getPos(ro, b.start, b.end); + + if (sd) { + r = t.dom.doc.createRange(); + r.setStart(sd.startNode, sd.startOffset); + r.setEnd(sd.endNode, sd.endOffset); + s.removeAllRanges(); + s.addRange(r); + } + + if (!tinymce.isOpera) + t.win.focus(); + } catch (ex) { + // Ignore + } + } + } + }, + + /** + * Selects the specified element. This will place the start and end of the selection range around the element. + * + * @param {Element} n HMTL DOM element to select. + * @param {} c 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(n, c) { + var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document; + + function find(n, start) { + var walker, o; + + if (n) { + walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + // Find first/last non empty text node + while (n = walker.nextNode()) { + o = n; + + if (tinymce.trim(n.nodeValue).length != 0) { + if (start) + return n; + else + o = n; + } + } + } + + return o; + }; + + if (isIE) { + try { + b = d.body; + + if (/^(IMG|TABLE)$/.test(n.nodeName)) { + r = b.createControlRange(); + r.addElement(n); + } else { + r = b.createTextRange(); + r.moveToElementText(n); + } + + r.select(); + } catch (ex) { + // Throws illigal agrument in IE some times + } + } else { + if (c) { + fn = find(n, 1) || t.dom.select('br:first', n)[0]; + ln = find(n, 0) || t.dom.select('br:last', n)[0]; + + if (fn && ln) { + r = d.createRange(); + + if (fn.nodeName == 'BR') + r.setStartBefore(fn); + else + r.setStart(fn, 0); + + if (ln.nodeName == 'BR') + r.setEndBefore(ln); + else + r.setEnd(ln, ln.nodeValue.length); + } else + r.selectNode(n); + } else + r.selectNode(n); + + t.setRng(r); + } + + return n; + }, + + /** + * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection. + * + * @return {bool} 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; + + return !s || r.boundingWidth == 0 || r.collapsed; + }, + + /** + * Collapse the selection to start or end of range. + * + * @param {bool} 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. + * + * @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. + * + * @param {bool} 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 = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange(); + + return r; + }, + + /** + * Changes the selection to the specified DOM range. + * + * @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. + * + * @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. + * + * @return {Element} Currently selected element or common ancestor element. + */ + getNode : function() { + var t = this, r = t.getRng(), s = t.getSel(), e; + + if (!isIE) { + // Range maybe lost after the editor is made visible again + if (!r) + return t.dom.getRoot(); + + e = r.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!r.collapsed) { + // If the anchor node is a element instead of a text node then return this element + if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1) + return s.anchorNode.childNodes[s.anchorOffset]; + + if (r.startContainer == r.endContainer) { + if (r.startOffset - r.endOffset < 2) { + if (r.startContainer.hasChildNodes()) + e = r.startContainer.childNodes[r.startOffset]; + } + } + } + + return t.dom.getParent(e, '*'); + } + + return r.item ? r.item(0) : r.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); diff --git a/js/tiny_mce/classes/dom/Serializer.js b/js/tiny_mce/classes/dom/Serializer.js new file mode 100644 index 0000000000..65717ecaa5 --- /dev/null +++ b/js/tiny_mce/classes/dom/Serializer.js @@ -0,0 +1,979 @@ +/** + * $Id: Serializer.js 1173 2009-06-29 14:44:25Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(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'); + }; + + /**#@+ + * @class 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. + * @member tinymce.dom.Serializer + */ + tinymce.create('tinymce.dom.Serializer', { + /** + * Constucts a new DOM serializer class. + * + * @constructor + * @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', + bool_attrs : /(checked|disabled|readonly|selected|nowrap)/, + valid_elements : '*[*]', + extended_valid_elements : 0, + valid_child_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, + font_size_style_values : 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; + + 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 '' + c + '>'; + + 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 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 ]*>)(.*?)(<\/script>)/g}, + {pattern : /(]*>)(.*?)(<\/noscript>)/g}, + {pattern : /("});j=j.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(h,l,k){return""})}j=j.replace(//g,"");j=j.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi,function(h,l){function k(o,n,q){var p=q;if(h.indexOf("mce_"+n)!=-1){return o}if(n=="style"){if(g._isRes(q)){return o}if(i.hex_colors){p=p.replace(/rgb\([^\)]+\)/g,function(m){return g.toHex(m)})}if(i.url_converter){p=p.replace(/url\([\'\"]?([^\)\'\"]+)\)/g,function(m,r){return"url("+g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(r),n,l))+")"})}}else{if(n!="coords"&&n!="shape"){if(i.url_converter){p=g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(q),n,l))}}}return" "+n+'="'+q+'" mce_'+n+'="'+p+'"'}h=h.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi,k);h=h.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi,k);return h.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi,k)})}return j},getOuterHTML:function(f){var g;f=this.get(f);if(!f){return null}if(f.outerHTML!==undefined){return f.outerHTML}g=(f.ownerDocument||this.doc).createElement("body");g.appendChild(f.cloneNode(true));return g.innerHTML},setOuterHTML:function(i,g,j){var f=this;return this.run(i,function(h){var l,k;h=f.get(h);j=j||h.ownerDocument||f.doc;if(a&&h.nodeType==1){h.outerHTML=g}else{k=j.createElement("body");k.innerHTML=g;l=k.lastChild;while(l){f.insertAfter(l.cloneNode(true),h);l=l.previousSibling}f.remove(h)}})},decode:function(g){var h,i,f;if(/&[^;]+;/.test(g)){h=this.doc.createElement("div");h.innerHTML=g;i=h.firstChild;f="";if(i){do{f+=i.nodeValue}while(i.nextSibling)}return f||g}return g},encode:function(f){return f?(""+f).replace(/[<>&\"]/g,function(h,g){switch(h){case"&":return"&";case'"':return""";case"<":return"<";case">":return">"}return h}):f},insertAfter:function(h,g){var f=this;g=f.get(g);return this.run(h,function(k){var j,i;j=g.parentNode;i=g.nextSibling;if(i){j.insertBefore(k,i)}else{j.appendChild(k)}return k})},isBlock:function(f){if(f.nodeType&&f.nodeType!==1){return false}f=f.nodeName||f;return/^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(f)},replace:function(i,h,f){var g=this;if(b(h,"array")){i=i.cloneNode(true)}return g.run(h,function(j){if(f){e(j.childNodes,function(k){i.appendChild(k.cloneNode(true))})}if(g.fixPsuedoLeaks&&j.nodeType===1){j.parentNode.insertBefore(i,j);g.remove(j);return i}return j.parentNode.replaceChild(i,j)})},findCommonAncestor:function(h,f){var i=h,g;while(i){g=f;while(g&&i!=g){g=g.parentNode}if(i==g){break}i=i.parentNode}if(!i&&h.ownerDocument){return h.ownerDocument.documentElement}return i},toHex:function(f){var h=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(f);function g(i){i=parseInt(i).toString(16);return i.length>1?i:"0"+i}if(h){f="#"+g(h[1])+g(h[2])+g(h[3]);return f}return f},getClasses:function(){var l=this,g=[],k,m={},n=l.settings.class_filter,j;if(l.classes){return l.classes}function o(f){e(f.imports,function(i){o(i)});e(f.cssRules||f.rules,function(i){switch(i.type||1){case 1:if(i.selectorText){e(i.selectorText.split(","),function(p){p=p.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(p)||!/\.[\w\-]+$/.test(p)){return}j=p;p=p.replace(/.*\.([a-z0-9_\-]+).*/i,"$1");if(n&&!(p=n(p,j))){return}if(!m[p]){g.push({"class":p});m[p]=1}})}break;case 3:o(i.styleSheet);break}})}try{e(l.doc.styleSheets,o)}catch(h){}if(g.length>0){l.classes=g}return g},run:function(j,i,h){var g=this,k;if(g.doc&&typeof(j)==="string"){j=g.get(j)}if(!j){return false}h=h||this;if(!j.nodeType&&(j.length||j.length===0)){k=[];e(j,function(l,f){if(l){if(typeof(l)=="string"){l=g.doc.getElementById(l)}k.push(i.call(h,l,f))}});return k}return i.call(h,j)},getAttribs:function(g){var f;g=this.get(g);if(!g){return[]}if(a){f=[];if(g.nodeName=="OBJECT"){return g.attributes}g.cloneNode(false).outerHTML.replace(/([a-z0-9\:\-_]+)=/gi,function(i,h){f.push({specified:1,nodeName:h})});return f}return g.attributes},destroy:function(g){var f=this;if(f.events){f.events.destroy()}f.win=f.doc=f.root=f.events=null;if(!g){c.removeUnload(f.destroy)}},createRng:function(){var f=this.doc;return f.createRange?f.createRange():new c.dom.Range(this)},split:function(k,j,n){var o=this,f=o.createRng(),l,i,m;function g(q,p){q=q[p];if(q&&q[p]&&q[p].nodeType==1&&h(q[p])){o.remove(q[p])}}function h(p){p=o.getOuterHTML(p);p=p.replace(/<(img|hr|table)/gi,"-");p=p.replace(/<[^>]+>/g,"");return p.replace(/[ \t\r\n]+| | /g,"")==""}if(k&&j){f.setStartBefore(k);f.setEndBefore(j);l=f.extractContents();f=o.createRng();f.setStartAfter(j);f.setEndAfter(k);i=f.extractContents();m=k.parentNode;g(l,"lastChild");if(!h(l)){m.insertBefore(l,k)}if(n){m.replaceChild(n,j)}else{m.insertBefore(j,k)}g(i,"firstChild");if(!h(i)){m.insertBefore(i,k)}o.remove(k);return n||j}},bind:function(j,f,i,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.add(j,f,i,h||this)},unbind:function(i,f,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.remove(i,f,h)},_isRes:function(f){return/^(top|left|bottom|right|width|height)/i.test(f)||/;\s*(top|left|bottom|right|width|height)/i.test(f)}});c.DOM=new c.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(f){var h=0,c=1,e=2,d=tinymce.extend;function g(m,k){var j,l;if(m.parentNode!=k){return -1}for(l=k.firstChild,j=0;l!=m;l=l.nextSibling){j++}return j}function b(k){var j=0;while(k.previousSibling){j++;k=k.previousSibling}return j}function i(j,k){var l;if(j.nodeType==3){return j}if(k<0){return j}l=j.firstChild;while(l!=null&&k>0){--k;l=l.nextSibling}if(l!=null){return l}return j}function a(k){var j=k.doc;d(this,{dom:k,startContainer:j,startOffset:0,endContainer:j,endOffset:0,collapsed:true,commonAncestorContainer:j,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3})}d(a.prototype,{setStart:function(k,j){this._setEndPoint(true,k,j)},setEnd:function(k,j){this._setEndPoint(false,k,j)},setStartBefore:function(j){this.setStart(j.parentNode,b(j))},setStartAfter:function(j){this.setStart(j.parentNode,b(j)+1)},setEndBefore:function(j){this.setEnd(j.parentNode,b(j))},setEndAfter:function(j){this.setEnd(j.parentNode,b(j)+1)},collapse:function(k){var j=this;if(k){j.endContainer=j.startContainer;j.endOffset=j.startOffset}else{j.startContainer=j.endContainer;j.startOffset=j.endOffset}j.collapsed=true},selectNode:function(j){this.setStartBefore(j);this.setEndAfter(j)},selectNodeContents:function(j){this.setStart(j,0);this.setEnd(j,j.nodeType===1?j.childNodes.length:j.nodeValue.length)},compareBoundaryPoints:function(m,n){var l=this,p=l.startContainer,o=l.startOffset,k=l.endContainer,j=l.endOffset;if(m===0){return l._compareBoundaryPoints(p,o,p,o)}if(m===1){return l._compareBoundaryPoints(p,o,k,j)}if(m===2){return l._compareBoundaryPoints(k,j,k,j)}if(m===3){return l._compareBoundaryPoints(k,j,p,o)}},deleteContents:function(){this._traverse(e)},extractContents:function(){return this._traverse(h)},cloneContents:function(){return this._traverse(c)},insertNode:function(m){var j=this,l,k;if(m.nodeType===3||m.nodeType===4){l=j.startContainer.splitText(j.startOffset);j.startContainer.parentNode.insertBefore(m,l)}else{if(j.startContainer.childNodes.length>0){k=j.startContainer.childNodes[j.startOffset]}j.startContainer.insertBefore(m,k)}},surroundContents:function(l){var j=this,k=j.extractContents();j.insertNode(l);l.appendChild(k);j.selectNode(l)},cloneRange:function(){var j=this;return d(new a(j.dom),{startContainer:j.startContainer,startOffset:j.startOffset,endContainer:j.endContainer,endOffset:j.endOffset,collapsed:j.collapsed,commonAncestorContainer:j.commonAncestorContainer})},_isCollapsed:function(){return(this.startContainer==this.endContainer&&this.startOffset==this.endOffset)},_compareBoundaryPoints:function(m,p,k,o){var q,l,j,r,t,s;if(m==k){if(p==o){return 0}else{if(p0){l.collapse(k)}}l.collapsed=l._isCollapsed();l.commonAncestorContainer=l.dom.findCommonAncestor(l.startContainer,l.endContainer)},_traverse:function(r){var s=this,q,m=0,v=0,k,o,l,n,j,u;if(s.startContainer==s.endContainer){return s._traverseSameContainer(r)}for(q=s.endContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.startContainer){return s._traverseCommonStartContainer(q,r)}++m}for(q=s.startContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.endContainer){return s._traverseCommonEndContainer(q,r)}++v}o=v-m;l=s.startContainer;while(o>0){l=l.parentNode;o--}n=s.endContainer;while(o<0){n=n.parentNode;o++}for(j=l.parentNode,u=n.parentNode;j!=u;j=j.parentNode,u=u.parentNode){l=j;n=u}return s._traverseCommonAncestors(l,n,r)},_traverseSameContainer:function(o){var r=this,q,u,j,k,l,p,m;if(o!=e){q=r.dom.doc.createDocumentFragment()}if(r.startOffset==r.endOffset){return q}if(r.startContainer.nodeType==3){u=r.startContainer.nodeValue;j=u.substring(r.startOffset,r.endOffset);if(o!=c){r.startContainer.deleteData(r.startOffset,r.endOffset-r.startOffset);r.collapse(true)}if(o==e){return null}q.appendChild(r.dom.doc.createTextNode(j));return q}k=i(r.startContainer,r.startOffset);l=r.endOffset-r.startOffset;while(l>0){p=k.nextSibling;m=r._traverseFullySelected(k,o);if(q){q.appendChild(m)}--l;k=p}if(o!=c){r.collapse(true)}return q},_traverseCommonStartContainer:function(j,p){var s=this,r,k,l,m,q,o;if(p!=e){r=s.dom.doc.createDocumentFragment()}k=s._traverseRightBoundary(j,p);if(r){r.appendChild(k)}l=g(j,s.startContainer);m=l-s.startOffset;if(m<=0){if(p!=c){s.setEndBefore(j);s.collapse(false)}return r}k=j.previousSibling;while(m>0){q=k.previousSibling;o=s._traverseFullySelected(k,p);if(r){r.insertBefore(o,r.firstChild)}--m;k=q}if(p!=c){s.setEndBefore(j);s.collapse(false)}return r},_traverseCommonEndContainer:function(m,p){var s=this,r,o,j,k,q,l;if(p!=e){r=s.dom.doc.createDocumentFragment()}j=s._traverseLeftBoundary(m,p);if(r){r.appendChild(j)}o=g(m,s.endContainer);++o;k=s.endOffset-o;j=m.nextSibling;while(k>0){q=j.nextSibling;l=s._traverseFullySelected(j,p);if(r){r.appendChild(l)}--k;j=q}if(p!=c){s.setStartAfter(m);s.collapse(true)}return r},_traverseCommonAncestors:function(p,j,s){var w=this,l,v,o,q,r,k,u,m;if(s!=e){v=w.dom.doc.createDocumentFragment()}l=w._traverseLeftBoundary(p,s);if(v){v.appendChild(l)}o=p.parentNode;q=g(p,o);r=g(j,o);++q;k=r-q;u=p.nextSibling;while(k>0){m=u.nextSibling;l=w._traverseFullySelected(u,s);if(v){v.appendChild(l)}u=m;--k}l=w._traverseRightBoundary(j,s);if(v){v.appendChild(l)}if(s!=c){w.setStartAfter(p);w.collapse(true)}return v},_traverseRightBoundary:function(p,q){var s=this,l=i(s.endContainer,s.endOffset-1),r,o,n,j,k;var m=l!=s.endContainer;if(l==p){return s._traverseNode(l,m,false,q)}r=l.parentNode;o=s._traverseNode(r,false,false,q);while(r!=null){while(l!=null){n=l.previousSibling;j=s._traverseNode(l,m,false,q);if(q!=e){o.insertBefore(j,o.firstChild)}m=true;l=n}if(r==p){return o}l=r.previousSibling;r=r.parentNode;k=s._traverseNode(r,false,false,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseLeftBoundary:function(p,q){var s=this,m=i(s.startContainer,s.startOffset);var n=m!=s.startContainer,r,o,l,j,k;if(m==p){return s._traverseNode(m,n,true,q)}r=m.parentNode;o=s._traverseNode(r,false,true,q);while(r!=null){while(m!=null){l=m.nextSibling;j=s._traverseNode(m,n,true,q);if(q!=e){o.appendChild(j)}n=true;m=l}if(r==p){return o}m=r.nextSibling;r=r.parentNode;k=s._traverseNode(r,false,true,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseNode:function(j,o,r,s){var u=this,m,l,p,k,q;if(o){return u._traverseFullySelected(j,s)}if(j.nodeType==3){m=j.nodeValue;if(r){k=u.startOffset;l=m.substring(k);p=m.substring(0,k)}else{k=u.endOffset;l=m.substring(0,k);p=m.substring(k)}if(s!=c){j.nodeValue=p}if(s==e){return null}q=j.cloneNode(false);q.nodeValue=l;return q}if(s==e){return null}return j.cloneNode(false)},_traverseFullySelected:function(l,k){var j=this;if(k!=e){return k==c?l.cloneNode(true):l}l.parentNode.removeChild(l);return null}});f.Range=a})(tinymce.dom);(function(){function a(e){var d=this,h="\uFEFF",b,g;function c(j,i){if(j&&i){if(j.item&&i.item&&j.item(0)===i.item(0)){return 1}if(j.isEqual&&i.isEqual&&i.isEqual(j)){return 1}}return 0}function f(){var m=e.dom,j=e.getRng(),s=m.createRng(),p,k,n,q,o,l;function i(v){var t=v.parentNode.childNodes,u;for(u=t.length-1;u>=0;u--){if(t[u]==v){return u}}return -1}function r(v){var t=j.duplicate(),B,y,u,w,x=0,z=0,A,C;t.collapse(v);B=t.parentElement();t.pasteHTML(h);u=B.childNodes;for(y=0;y0&&(w.nodeType!==3||u[y-1].nodeType!==3)){z++}if(w.nodeType===3){A=w.nodeValue.indexOf(h);if(A!==-1){x+=A;break}x+=w.nodeValue.length}else{x=0}}t.moveStart("character",-1);t.text="";return{index:z,offset:x,parent:B}}n=j.item?j.item(0):j.parentElement();if(n.ownerDocument!=m.doc){return s}if(j.item||!n.hasChildNodes()){s.setStart(n.parentNode,i(n));s.setEnd(s.startContainer,s.startOffset+1);return s}l=e.isCollapsed();p=r(true);k=r(false);p.parent.normalize();k.parent.normalize();q=p.parent.childNodes[Math.min(p.index,p.parent.childNodes.length-1)];if(q.nodeType!=3){s.setStart(p.parent,p.index)}else{s.setStart(p.parent.childNodes[p.index],p.offset)}o=k.parent.childNodes[Math.min(k.index,k.parent.childNodes.length-1)];if(o.nodeType!=3){if(!l){k.index++}s.setEnd(k.parent,k.index)}else{s.setEnd(k.parent.childNodes[k.index],k.offset)}if(!l){q=s.startContainer;if(q.nodeType==1){s.setStart(q,Math.min(s.startOffset,q.childNodes.length))}o=s.endContainer;if(o.nodeType==1){s.setEnd(o,Math.min(s.endOffset,o.childNodes.length))}}d.addRange(s);return s}this.addRange=function(j){var o,m=e.dom.doc.body,p,k,q,l,n,i;q=j.startContainer;l=j.startOffset;n=j.endContainer;i=j.endOffset;o=m.createTextRange();q=q.nodeType==1?q.childNodes[Math.min(l,q.childNodes.length-1)]:q;n=n.nodeType==1?n.childNodes[Math.min(l==i?i:i-1,n.childNodes.length-1)]:n;if(q==n&&q.nodeType==1){if(/^(IMG|TABLE)$/.test(q.nodeName)&&l!=i){o=m.createControlRange();o.addElement(q)}else{o=m.createTextRange();if(!q.hasChildNodes()&&q.canHaveHTML){q.innerHTML=h}o.moveToElementText(q);if(q.innerHTML==h){o.collapse(true);q.removeChild(q.firstChild)}}if(l==i){o.collapse(i<=j.endContainer.childNodes.length-1)}o.select();return}function r(t,v){var u,s,w;if(t.nodeType!=3){return -1}u=t.nodeValue;s=m.createTextRange();t.nodeValue=u.substring(0,v)+h+u.substring(v);s.moveToElementText(t.parentNode);s.findText(h);w=Math.abs(s.moveStart("character",-1048575));t.nodeValue=u;return w}if(j.collapsed){pos=r(q,l);o=m.createTextRange();o.move("character",pos);o.select();return}else{if(q==n&&q.nodeType==3){p=r(q,l);o.move("character",p);o.moveEnd("character",i-l);o.select();return}p=r(q,l);k=r(n,i);o=m.createTextRange();if(p==-1){o.moveToElementText(q);p=0}else{o.move("character",p)}tmpRng=m.createTextRange();if(k==-1){tmpRng.moveToElementText(n)}else{tmpRng.move("character",k)}o.setEndPoint("EndToEnd",tmpRng);o.select();return}};this.getRangeAt=function(){if(!b||!c(g,e.getRng())){b=f();g=e.getRng()}return b};this.destroy=function(){g=b=null}}tinymce.dom.TridentSelection=a})();(function(){var p=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,i=0,d=Object.prototype.toString,n=false;var b=function(D,t,A,v){A=A||[];var e=t=t||document;if(t.nodeType!==1&&t.nodeType!==9){return[]}if(!D||typeof D!=="string"){return A}var B=[],C,y,G,F,z,s,r=true,w=o(t);p.lastIndex=0;while((C=p.exec(D))!==null){B.push(C[1]);if(C[2]){s=RegExp.rightContext;break}}if(B.length>1&&j.exec(D)){if(B.length===2&&f.relative[B[0]]){y=g(B[0]+B[1],t)}else{y=f.relative[B[0]]?[t]:b(B.shift(),t);while(B.length){D=B.shift();if(f.relative[D]){D+=B.shift()}y=g(D,y)}}}else{if(!v&&B.length>1&&t.nodeType===9&&!w&&f.match.ID.test(B[0])&&!f.match.ID.test(B[B.length-1])){var H=b.find(B.shift(),t,w);t=H.expr?b.filter(H.expr,H.set)[0]:H.set[0]}if(t){var H=v?{expr:B.pop(),set:a(v)}:b.find(B.pop(),B.length===1&&(B[0]==="~"||B[0]==="+")&&t.parentNode?t.parentNode:t,w);y=H.expr?b.filter(H.expr,H.set):H.set;if(B.length>0){G=a(y)}else{r=false}while(B.length){var u=B.pop(),x=u;if(!f.relative[u]){u=""}else{x=B.pop()}if(x==null){x=t}f.relative[u](G,x,w)}}else{G=B=[]}}if(!G){G=y}if(!G){throw"Syntax error, unrecognized expression: "+(u||D)}if(d.call(G)==="[object Array]"){if(!r){A.push.apply(A,G)}else{if(t&&t.nodeType===1){for(var E=0;G[E]!=null;E++){if(G[E]&&(G[E]===true||G[E].nodeType===1&&h(t,G[E]))){A.push(y[E])}}}else{for(var E=0;G[E]!=null;E++){if(G[E]&&G[E].nodeType===1){A.push(y[E])}}}}}else{a(G,A)}if(s){b(s,e,A,v);b.uniqueSort(A)}return A};b.uniqueSort=function(r){if(c){n=false;r.sort(c);if(n){for(var e=1;e":function(w,r,x){var u=typeof r==="string";if(u&&!/\W/.test(r)){r=x?r:r.toUpperCase();for(var s=0,e=w.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,w){var t=u[1].replace(/\\/g,"");if(!w&&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(w,s,t,x){var r=s[1],u=f.filters[r];if(u){return u(w,t,s,x)}else{if(r==="contains"){return(w.textContent||w.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),w=e+"",u=t[2],r=t[4];return e==null?u==="!=":u==="="?w===r:u==="*="?w.indexOf(r)>=0:u==="~="?(" "+w+" ").indexOf(r)>=0:!r?w&&e!==false:u==="!="?w!=r:u==="^="?w.indexOf(r)===0:u==="$="?w.substr(w.length-r.length)===r:u==="|="?w===r||w.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,w){if(typeof v.getElementById!=="undefined"&&!w){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(w,v,t,u){v=v||document;if(!u&&v.nodeType===9&&!o(v)){try{return a(v.querySelectorAll(w),t)}catch(x){}}return e(w,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,w,v,A,x,z){var y=r=="previousSibling"&&!z;for(var t=0,s=A.length;t0){u=e;break}}}e=e[r]}A[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,x){var t=[],u="",v,s=x.nodeType?[x]:x;while((v=f.match.PSEUDO.exec(e))){u+=v[0];e=e.replace(f.match.PSEUDO,"")}e=f.relative[e]?e+"*":e;for(var w=0,r=s.length;w=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){var b=a.each;a.create("tinymce.dom.Element",{Element:function(g,e){var c=this,f,d;e=e||{};c.id=g;c.dom=f=e.dom||a.DOM;c.settings=e;if(!a.isIE){d=c.dom.get(c.id)}b(["getPos","getRect","getParent","add","setStyle","getStyle","setStyles","setAttrib","setAttribs","getAttrib","addClass","removeClass","hasClass","getOuterHTML","setOuterHTML","remove","show","hide","isHidden","setHTML","get"],function(h){c[h]=function(){var j=[g],k;for(k=0;k_';j.deleteContents();j.insertNode(f.getRng().createContextualFragment(i));l=f.dom.get("__caret");j=k.createRange();j.setStartBefore(l);j.setEndAfter(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(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(1);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.firstChild}return h}else{h=g.startContainer;if(h.nodeName=="BODY"){return h.firstChild}return f.dom.getParent(h,"*")}},getEnd:function(){var f=this,g=f.getRng(),h;if(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(0);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.lastChild}return h}else{h=g.endContainer;if(h.nodeName=="BODY"){return h.lastChild}return f.dom.getParent(h,"*")}},getBookmark:function(x){var j=this,m=j.getRng(),f,n,l,u=j.dom.getViewPort(j.win),v,p,z,o,w=-16777215,k,h=j.dom.getRoot(),g=0,i=0,y;n=u.x;l=u.y;if(x){return{rng:m,scrollX:n,scrollY:l}}if(a){if(m.item){v=m.item(0);d(j.dom.select(v.nodeName),function(s,r){if(v==s){p=r;return false}});return{tag:v.nodeName,index:p,scrollX:n,scrollY:l}}f=j.dom.doc.body.createTextRange();f.moveToElementText(h);f.collapse(true);z=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(true);p=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(false);o=Math.abs(f.move("character",w))-p;return{start:p-z,length:o,scrollX:n,scrollY:l}}v=j.getNode();k=j.getSel();if(!k){return null}if(v&&v.nodeName=="IMG"){return{scrollX:n,scrollY:l}}function q(A,D,t){var s=j.dom.doc.createTreeWalker(A,NodeFilter.SHOW_TEXT,null,false),E,B=0,C={};while((E=s.nextNode())!=null){if(E==D){C.start=B}if(E==t){C.end=B;return C}B+=e(E.nodeValue||"").length}return null}if(k.anchorNode==k.focusNode&&k.anchorOffset==k.focusOffset){v=q(h,k.anchorNode,k.focusNode);if(!v){return{scrollX:n,scrollY:l}}e(k.anchorNode.nodeValue||"").replace(/^\s+/,function(r){g=r.length});return{start:Math.max(v.start+k.anchorOffset-g,0),end:Math.max(v.end+k.focusOffset-g,0),scrollX:n,scrollY:l,beg:k.anchorOffset-g==0}}else{v=q(h,m.startContainer,m.endContainer);if(!v){return{scrollX:n,scrollY:l}}return{start:Math.max(v.start+m.startOffset-g,0),end:Math.max(v.end+m.endOffset-i,0),scrollX:n,scrollY:l,beg:m.startOffset-g==0}}},moveToBookmark:function(n){var o=this,g=o.getRng(),p=o.getSel(),j=o.dom.getRoot(),m,h,k;function i(q,t,D){var B=o.dom.doc.createTreeWalker(q,NodeFilter.SHOW_TEXT,null,false),x,s=0,A={},u,C,z,y;while((x=B.nextNode())!=null){z=y=0;k=x.nodeValue||"";h=e(k).length;s+=h;if(s>=t&&!A.startNode){u=t-(s-h);if(n.beg&&u>=h){continue}A.startNode=x;A.startOffset=u+y}if(s>=D){A.endNode=x;A.endOffset=D-(s-h)+y;return A}}return null}if(!n){return false}o.win.scrollTo(n.scrollX,n.scrollY);if(a){if(g=n.rng){try{g.select()}catch(l){}return true}o.win.focus();if(n.tag){g=j.createControlRange();d(o.dom.select(n.tag),function(r,q){if(q==n.index){g.addElement(r)}})}else{try{if(n.start<0){return true}g=p.createRange();g.moveToElementText(j);g.collapse(true);g.moveStart("character",n.start);g.moveEnd("character",n.length)}catch(f){return true}}try{g.select()}catch(l){}return true}if(!p){return false}if(n.rng){p.removeAllRanges();p.addRange(n.rng)}else{if(b(n.start)&&b(n.end)){try{m=i(j,n.start,n.end);if(m){g=o.dom.doc.createRange();g.setStart(m.startNode,m.startOffset);g.setEnd(m.endNode,m.endOffset);p.removeAllRanges();p.addRange(g)}if(!c.isOpera){o.win.focus()}}catch(l){}}}},select:function(g,l){var p=this,f=p.getRng(),q=p.getSel(),o,m,k,j=p.win.document;function h(u,t){var s,r;if(u){s=j.createTreeWalker(u,NodeFilter.SHOW_TEXT,null,false);while(u=s.nextNode()){r=u;if(c.trim(u.nodeValue).length!=0){if(t){return u}else{r=u}}}}return r}if(a){try{o=j.body;if(/^(IMG|TABLE)$/.test(g.nodeName)){f=o.createControlRange();f.addElement(g)}else{f=o.createTextRange();f.moveToElementText(g)}f.select()}catch(i){}}else{if(l){m=h(g,1)||p.dom.select("br:first",g)[0];k=h(g,0)||p.dom.select("br:last",g)[0];if(m&&k){f=j.createRange();if(m.nodeName=="BR"){f.setStartBefore(m)}else{f.setStart(m,0)}if(k.nodeName=="BR"){f.setEndBefore(k)}else{f.setEnd(k,k.nodeValue.length)}}else{f.selectNode(g)}}else{f.selectNode(g)}p.setRng(f)}return g},isCollapsed:function(){var f=this,h=f.getRng(),g=f.getSel();if(!h||h.item){return false}return !g||h.boundingWidth==0||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=a?g.win.document.body.createTextRange():g.win.document.createRange()}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 f=this,h=f.getRng(),g=f.getSel(),i;if(!a){if(!h){return f.dom.getRoot()}i=h.commonAncestorContainer;if(!h.collapsed){if(c.isWebKit&&g.anchorNode&&g.anchorNode.nodeType==1){return g.anchorNode.childNodes[g.anchorOffset]}if(h.startContainer==h.endContainer){if(h.startOffset-h.endOffset<2){if(h.startContainer.hasChildNodes()){i=h.startContainer.childNodes[h.startOffset]}}}}return f.dom.getParent(i,"*")}return h.item?h.item(0):h.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(""+b+">")}if(this.settings.indentation>0){this.writeRaw("\n")}}},writeFullEndElement:function(){if(this.tags.length>0){this._writeAttributesEnd();this.writeRaw(""+this.tags.pop()+">");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",bool_attrs:/(checked|disabled|readonly|selected|nowrap)/,valid_elements:"*[*]",extended_valid_elements:0,valid_child_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,font_size_style_values: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;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""+o+">"}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,y,w=["ol","ul"],u,t,q,k=/^(OL|UL)$/,z;function m(r,x){var o=x.split(","),p;while((r=r.previousSibling)!=null){for(p=0;p1){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]*>)(.*?)(<\/script>)/g},{pattern:/(]*>)(.*?)(<\/noscript>)/g},{pattern:/("});j=j.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(h,l,k){return""})}j=j.replace(//g,"");j=j.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi,function(h,l){function k(o,n,q){var p=q;if(h.indexOf("mce_"+n)!=-1){return o}if(n=="style"){if(g._isRes(q)){return o}if(i.hex_colors){p=p.replace(/rgb\([^\)]+\)/g,function(m){return g.toHex(m)})}if(i.url_converter){p=p.replace(/url\([\'\"]?([^\)\'\"]+)\)/g,function(m,r){return"url("+g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(r),n,l))+")"})}}else{if(n!="coords"&&n!="shape"){if(i.url_converter){p=g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(q),n,l))}}}return" "+n+'="'+q+'" mce_'+n+'="'+p+'"'}h=h.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi,k);h=h.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi,k);return h.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi,k)})}return j},getOuterHTML:function(f){var g;f=this.get(f);if(!f){return null}if(f.outerHTML!==undefined){return f.outerHTML}g=(f.ownerDocument||this.doc).createElement("body");g.appendChild(f.cloneNode(true));return g.innerHTML},setOuterHTML:function(i,g,j){var f=this;return this.run(i,function(h){var l,k;h=f.get(h);j=j||h.ownerDocument||f.doc;if(a&&h.nodeType==1){h.outerHTML=g}else{k=j.createElement("body");k.innerHTML=g;l=k.lastChild;while(l){f.insertAfter(l.cloneNode(true),h);l=l.previousSibling}f.remove(h)}})},decode:function(g){var h,i,f;if(/&[^;]+;/.test(g)){h=this.doc.createElement("div");h.innerHTML=g;i=h.firstChild;f="";if(i){do{f+=i.nodeValue}while(i.nextSibling)}return f||g}return g},encode:function(f){return f?(""+f).replace(/[<>&\"]/g,function(h,g){switch(h){case"&":return"&";case'"':return""";case"<":return"<";case">":return">"}return h}):f},insertAfter:function(h,g){var f=this;g=f.get(g);return this.run(h,function(k){var j,i;j=g.parentNode;i=g.nextSibling;if(i){j.insertBefore(k,i)}else{j.appendChild(k)}return k})},isBlock:function(f){if(f.nodeType&&f.nodeType!==1){return false}f=f.nodeName||f;return/^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(f)},replace:function(i,h,f){var g=this;if(b(h,"array")){i=i.cloneNode(true)}return g.run(h,function(j){if(f){e(j.childNodes,function(k){i.appendChild(k.cloneNode(true))})}if(g.fixPsuedoLeaks&&j.nodeType===1){j.parentNode.insertBefore(i,j);g.remove(j);return i}return j.parentNode.replaceChild(i,j)})},findCommonAncestor:function(h,f){var i=h,g;while(i){g=f;while(g&&i!=g){g=g.parentNode}if(i==g){break}i=i.parentNode}if(!i&&h.ownerDocument){return h.ownerDocument.documentElement}return i},toHex:function(f){var h=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(f);function g(i){i=parseInt(i).toString(16);return i.length>1?i:"0"+i}if(h){f="#"+g(h[1])+g(h[2])+g(h[3]);return f}return f},getClasses:function(){var l=this,g=[],k,m={},n=l.settings.class_filter,j;if(l.classes){return l.classes}function o(f){e(f.imports,function(i){o(i)});e(f.cssRules||f.rules,function(i){switch(i.type||1){case 1:if(i.selectorText){e(i.selectorText.split(","),function(p){p=p.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(p)||!/\.[\w\-]+$/.test(p)){return}j=p;p=p.replace(/.*\.([a-z0-9_\-]+).*/i,"$1");if(n&&!(p=n(p,j))){return}if(!m[p]){g.push({"class":p});m[p]=1}})}break;case 3:o(i.styleSheet);break}})}try{e(l.doc.styleSheets,o)}catch(h){}if(g.length>0){l.classes=g}return g},run:function(j,i,h){var g=this,k;if(g.doc&&typeof(j)==="string"){j=g.get(j)}if(!j){return false}h=h||this;if(!j.nodeType&&(j.length||j.length===0)){k=[];e(j,function(l,f){if(l){if(typeof(l)=="string"){l=g.doc.getElementById(l)}k.push(i.call(h,l,f))}});return k}return i.call(h,j)},getAttribs:function(g){var f;g=this.get(g);if(!g){return[]}if(a){f=[];if(g.nodeName=="OBJECT"){return g.attributes}g.cloneNode(false).outerHTML.replace(/([a-z0-9\:\-_]+)=/gi,function(i,h){f.push({specified:1,nodeName:h})});return f}return g.attributes},destroy:function(g){var f=this;if(f.events){f.events.destroy()}f.win=f.doc=f.root=f.events=null;if(!g){c.removeUnload(f.destroy)}},createRng:function(){var f=this.doc;return f.createRange?f.createRange():new c.dom.Range(this)},split:function(k,j,n){var o=this,f=o.createRng(),l,i,m;function g(q,p){q=q[p];if(q&&q[p]&&q[p].nodeType==1&&h(q[p])){o.remove(q[p])}}function h(p){p=o.getOuterHTML(p);p=p.replace(/<(img|hr|table)/gi,"-");p=p.replace(/<[^>]+>/g,"");return p.replace(/[ \t\r\n]+| | /g,"")==""}if(k&&j){f.setStartBefore(k);f.setEndBefore(j);l=f.extractContents();f=o.createRng();f.setStartAfter(j);f.setEndAfter(k);i=f.extractContents();m=k.parentNode;g(l,"lastChild");if(!h(l)){m.insertBefore(l,k)}if(n){m.replaceChild(n,j)}else{m.insertBefore(j,k)}g(i,"firstChild");if(!h(i)){m.insertBefore(i,k)}o.remove(k);return n||j}},bind:function(j,f,i,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.add(j,f,i,h||this)},unbind:function(i,f,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.remove(i,f,h)},_isRes:function(f){return/^(top|left|bottom|right|width|height)/i.test(f)||/;\s*(top|left|bottom|right|width|height)/i.test(f)}});c.DOM=new c.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(f){var h=0,c=1,e=2,d=tinymce.extend;function g(m,k){var j,l;if(m.parentNode!=k){return -1}for(l=k.firstChild,j=0;l!=m;l=l.nextSibling){j++}return j}function b(k){var j=0;while(k.previousSibling){j++;k=k.previousSibling}return j}function i(j,k){var l;if(j.nodeType==3){return j}if(k<0){return j}l=j.firstChild;while(l!=null&&k>0){--k;l=l.nextSibling}if(l!=null){return l}return j}function a(k){var j=k.doc;d(this,{dom:k,startContainer:j,startOffset:0,endContainer:j,endOffset:0,collapsed:true,commonAncestorContainer:j,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3})}d(a.prototype,{setStart:function(k,j){this._setEndPoint(true,k,j)},setEnd:function(k,j){this._setEndPoint(false,k,j)},setStartBefore:function(j){this.setStart(j.parentNode,b(j))},setStartAfter:function(j){this.setStart(j.parentNode,b(j)+1)},setEndBefore:function(j){this.setEnd(j.parentNode,b(j))},setEndAfter:function(j){this.setEnd(j.parentNode,b(j)+1)},collapse:function(k){var j=this;if(k){j.endContainer=j.startContainer;j.endOffset=j.startOffset}else{j.startContainer=j.endContainer;j.startOffset=j.endOffset}j.collapsed=true},selectNode:function(j){this.setStartBefore(j);this.setEndAfter(j)},selectNodeContents:function(j){this.setStart(j,0);this.setEnd(j,j.nodeType===1?j.childNodes.length:j.nodeValue.length)},compareBoundaryPoints:function(m,n){var l=this,p=l.startContainer,o=l.startOffset,k=l.endContainer,j=l.endOffset;if(m===0){return l._compareBoundaryPoints(p,o,p,o)}if(m===1){return l._compareBoundaryPoints(p,o,k,j)}if(m===2){return l._compareBoundaryPoints(k,j,k,j)}if(m===3){return l._compareBoundaryPoints(k,j,p,o)}},deleteContents:function(){this._traverse(e)},extractContents:function(){return this._traverse(h)},cloneContents:function(){return this._traverse(c)},insertNode:function(m){var j=this,l,k;if(m.nodeType===3||m.nodeType===4){l=j.startContainer.splitText(j.startOffset);j.startContainer.parentNode.insertBefore(m,l)}else{if(j.startContainer.childNodes.length>0){k=j.startContainer.childNodes[j.startOffset]}j.startContainer.insertBefore(m,k)}},surroundContents:function(l){var j=this,k=j.extractContents();j.insertNode(l);l.appendChild(k);j.selectNode(l)},cloneRange:function(){var j=this;return d(new a(j.dom),{startContainer:j.startContainer,startOffset:j.startOffset,endContainer:j.endContainer,endOffset:j.endOffset,collapsed:j.collapsed,commonAncestorContainer:j.commonAncestorContainer})},_isCollapsed:function(){return(this.startContainer==this.endContainer&&this.startOffset==this.endOffset)},_compareBoundaryPoints:function(m,p,k,o){var q,l,j,r,t,s;if(m==k){if(p==o){return 0}else{if(p0){l.collapse(k)}}l.collapsed=l._isCollapsed();l.commonAncestorContainer=l.dom.findCommonAncestor(l.startContainer,l.endContainer)},_traverse:function(r){var s=this,q,m=0,v=0,k,o,l,n,j,u;if(s.startContainer==s.endContainer){return s._traverseSameContainer(r)}for(q=s.endContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.startContainer){return s._traverseCommonStartContainer(q,r)}++m}for(q=s.startContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.endContainer){return s._traverseCommonEndContainer(q,r)}++v}o=v-m;l=s.startContainer;while(o>0){l=l.parentNode;o--}n=s.endContainer;while(o<0){n=n.parentNode;o++}for(j=l.parentNode,u=n.parentNode;j!=u;j=j.parentNode,u=u.parentNode){l=j;n=u}return s._traverseCommonAncestors(l,n,r)},_traverseSameContainer:function(o){var r=this,q,u,j,k,l,p,m;if(o!=e){q=r.dom.doc.createDocumentFragment()}if(r.startOffset==r.endOffset){return q}if(r.startContainer.nodeType==3){u=r.startContainer.nodeValue;j=u.substring(r.startOffset,r.endOffset);if(o!=c){r.startContainer.deleteData(r.startOffset,r.endOffset-r.startOffset);r.collapse(true)}if(o==e){return null}q.appendChild(r.dom.doc.createTextNode(j));return q}k=i(r.startContainer,r.startOffset);l=r.endOffset-r.startOffset;while(l>0){p=k.nextSibling;m=r._traverseFullySelected(k,o);if(q){q.appendChild(m)}--l;k=p}if(o!=c){r.collapse(true)}return q},_traverseCommonStartContainer:function(j,p){var s=this,r,k,l,m,q,o;if(p!=e){r=s.dom.doc.createDocumentFragment()}k=s._traverseRightBoundary(j,p);if(r){r.appendChild(k)}l=g(j,s.startContainer);m=l-s.startOffset;if(m<=0){if(p!=c){s.setEndBefore(j);s.collapse(false)}return r}k=j.previousSibling;while(m>0){q=k.previousSibling;o=s._traverseFullySelected(k,p);if(r){r.insertBefore(o,r.firstChild)}--m;k=q}if(p!=c){s.setEndBefore(j);s.collapse(false)}return r},_traverseCommonEndContainer:function(m,p){var s=this,r,o,j,k,q,l;if(p!=e){r=s.dom.doc.createDocumentFragment()}j=s._traverseLeftBoundary(m,p);if(r){r.appendChild(j)}o=g(m,s.endContainer);++o;k=s.endOffset-o;j=m.nextSibling;while(k>0){q=j.nextSibling;l=s._traverseFullySelected(j,p);if(r){r.appendChild(l)}--k;j=q}if(p!=c){s.setStartAfter(m);s.collapse(true)}return r},_traverseCommonAncestors:function(p,j,s){var w=this,l,v,o,q,r,k,u,m;if(s!=e){v=w.dom.doc.createDocumentFragment()}l=w._traverseLeftBoundary(p,s);if(v){v.appendChild(l)}o=p.parentNode;q=g(p,o);r=g(j,o);++q;k=r-q;u=p.nextSibling;while(k>0){m=u.nextSibling;l=w._traverseFullySelected(u,s);if(v){v.appendChild(l)}u=m;--k}l=w._traverseRightBoundary(j,s);if(v){v.appendChild(l)}if(s!=c){w.setStartAfter(p);w.collapse(true)}return v},_traverseRightBoundary:function(p,q){var s=this,l=i(s.endContainer,s.endOffset-1),r,o,n,j,k;var m=l!=s.endContainer;if(l==p){return s._traverseNode(l,m,false,q)}r=l.parentNode;o=s._traverseNode(r,false,false,q);while(r!=null){while(l!=null){n=l.previousSibling;j=s._traverseNode(l,m,false,q);if(q!=e){o.insertBefore(j,o.firstChild)}m=true;l=n}if(r==p){return o}l=r.previousSibling;r=r.parentNode;k=s._traverseNode(r,false,false,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseLeftBoundary:function(p,q){var s=this,m=i(s.startContainer,s.startOffset);var n=m!=s.startContainer,r,o,l,j,k;if(m==p){return s._traverseNode(m,n,true,q)}r=m.parentNode;o=s._traverseNode(r,false,true,q);while(r!=null){while(m!=null){l=m.nextSibling;j=s._traverseNode(m,n,true,q);if(q!=e){o.appendChild(j)}n=true;m=l}if(r==p){return o}m=r.nextSibling;r=r.parentNode;k=s._traverseNode(r,false,true,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseNode:function(j,o,r,s){var u=this,m,l,p,k,q;if(o){return u._traverseFullySelected(j,s)}if(j.nodeType==3){m=j.nodeValue;if(r){k=u.startOffset;l=m.substring(k);p=m.substring(0,k)}else{k=u.endOffset;l=m.substring(0,k);p=m.substring(k)}if(s!=c){j.nodeValue=p}if(s==e){return null}q=j.cloneNode(false);q.nodeValue=l;return q}if(s==e){return null}return j.cloneNode(false)},_traverseFullySelected:function(l,k){var j=this;if(k!=e){return k==c?l.cloneNode(true):l}l.parentNode.removeChild(l);return null}});f.Range=a})(tinymce.dom);(function(){function a(e){var d=this,h="\uFEFF",b,g;function c(j,i){if(j&&i){if(j.item&&i.item&&j.item(0)===i.item(0)){return 1}if(j.isEqual&&i.isEqual&&i.isEqual(j)){return 1}}return 0}function f(){var m=e.dom,j=e.getRng(),s=m.createRng(),p,k,n,q,o,l;function i(v){var t=v.parentNode.childNodes,u;for(u=t.length-1;u>=0;u--){if(t[u]==v){return u}}return -1}function r(v){var t=j.duplicate(),B,y,u,w,x=0,z=0,A,C;t.collapse(v);B=t.parentElement();t.pasteHTML(h);u=B.childNodes;for(y=0;y0&&(w.nodeType!==3||u[y-1].nodeType!==3)){z++}if(w.nodeType===3){A=w.nodeValue.indexOf(h);if(A!==-1){x+=A;break}x+=w.nodeValue.length}else{x=0}}t.moveStart("character",-1);t.text="";return{index:z,offset:x,parent:B}}n=j.item?j.item(0):j.parentElement();if(n.ownerDocument!=m.doc){return s}if(j.item||!n.hasChildNodes()){s.setStart(n.parentNode,i(n));s.setEnd(s.startContainer,s.startOffset+1);return s}l=e.isCollapsed();p=r(true);k=r(false);p.parent.normalize();k.parent.normalize();q=p.parent.childNodes[Math.min(p.index,p.parent.childNodes.length-1)];if(q.nodeType!=3){s.setStart(p.parent,p.index)}else{s.setStart(p.parent.childNodes[p.index],p.offset)}o=k.parent.childNodes[Math.min(k.index,k.parent.childNodes.length-1)];if(o.nodeType!=3){if(!l){k.index++}s.setEnd(k.parent,k.index)}else{s.setEnd(k.parent.childNodes[k.index],k.offset)}if(!l){q=s.startContainer;if(q.nodeType==1){s.setStart(q,Math.min(s.startOffset,q.childNodes.length))}o=s.endContainer;if(o.nodeType==1){s.setEnd(o,Math.min(s.endOffset,o.childNodes.length))}}d.addRange(s);return s}this.addRange=function(j){var o,m=e.dom.doc.body,p,k,q,l,n,i;q=j.startContainer;l=j.startOffset;n=j.endContainer;i=j.endOffset;o=m.createTextRange();q=q.nodeType==1?q.childNodes[Math.min(l,q.childNodes.length-1)]:q;n=n.nodeType==1?n.childNodes[Math.min(l==i?i:i-1,n.childNodes.length-1)]:n;if(q==n&&q.nodeType==1){if(/^(IMG|TABLE)$/.test(q.nodeName)&&l!=i){o=m.createControlRange();o.addElement(q)}else{o=m.createTextRange();if(!q.hasChildNodes()&&q.canHaveHTML){q.innerHTML=h}o.moveToElementText(q);if(q.innerHTML==h){o.collapse(true);q.removeChild(q.firstChild)}}if(l==i){o.collapse(i<=j.endContainer.childNodes.length-1)}o.select();return}function r(t,v){var u,s,w;if(t.nodeType!=3){return -1}u=t.nodeValue;s=m.createTextRange();t.nodeValue=u.substring(0,v)+h+u.substring(v);s.moveToElementText(t.parentNode);s.findText(h);w=Math.abs(s.moveStart("character",-1048575));t.nodeValue=u;return w}if(j.collapsed){pos=r(q,l);o=m.createTextRange();o.move("character",pos);o.select();return}else{if(q==n&&q.nodeType==3){p=r(q,l);o.move("character",p);o.moveEnd("character",i-l);o.select();return}p=r(q,l);k=r(n,i);o=m.createTextRange();if(p==-1){o.moveToElementText(q);p=0}else{o.move("character",p)}tmpRng=m.createTextRange();if(k==-1){tmpRng.moveToElementText(n)}else{tmpRng.move("character",k)}o.setEndPoint("EndToEnd",tmpRng);o.select();return}};this.getRangeAt=function(){if(!b||!c(g,e.getRng())){b=f();g=e.getRng()}return b};this.destroy=function(){g=b=null}}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){var b=a.each;a.create("tinymce.dom.Element",{Element:function(g,e){var c=this,f,d;e=e||{};c.id=g;c.dom=f=e.dom||a.DOM;c.settings=e;if(!a.isIE){d=c.dom.get(c.id)}b(["getPos","getRect","getParent","add","setStyle","getStyle","setStyles","setAttrib","setAttribs","getAttrib","addClass","removeClass","hasClass","getOuterHTML","setOuterHTML","remove","show","hide","isHidden","setHTML","get"],function(h){c[h]=function(){var j=[g],k;for(k=0;k_';j.deleteContents();j.insertNode(f.getRng().createContextualFragment(i));l=f.dom.get("__caret");j=k.createRange();j.setStartBefore(l);j.setEndAfter(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(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(1);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.firstChild}return h}else{h=g.startContainer;if(h.nodeName=="BODY"){return h.firstChild}return f.dom.getParent(h,"*")}},getEnd:function(){var f=this,g=f.getRng(),h;if(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(0);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.lastChild}return h}else{h=g.endContainer;if(h.nodeName=="BODY"){return h.lastChild}return f.dom.getParent(h,"*")}},getBookmark:function(x){var j=this,m=j.getRng(),f,n,l,u=j.dom.getViewPort(j.win),v,p,z,o,w=-16777215,k,h=j.dom.getRoot(),g=0,i=0,y;n=u.x;l=u.y;if(x){return{rng:m,scrollX:n,scrollY:l}}if(a){if(m.item){v=m.item(0);d(j.dom.select(v.nodeName),function(s,r){if(v==s){p=r;return false}});return{tag:v.nodeName,index:p,scrollX:n,scrollY:l}}f=j.dom.doc.body.createTextRange();f.moveToElementText(h);f.collapse(true);z=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(true);p=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(false);o=Math.abs(f.move("character",w))-p;return{start:p-z,length:o,scrollX:n,scrollY:l}}v=j.getNode();k=j.getSel();if(!k){return null}if(v&&v.nodeName=="IMG"){return{scrollX:n,scrollY:l}}function q(A,D,t){var s=j.dom.doc.createTreeWalker(A,NodeFilter.SHOW_TEXT,null,false),E,B=0,C={};while((E=s.nextNode())!=null){if(E==D){C.start=B}if(E==t){C.end=B;return C}B+=e(E.nodeValue||"").length}return null}if(k.anchorNode==k.focusNode&&k.anchorOffset==k.focusOffset){v=q(h,k.anchorNode,k.focusNode);if(!v){return{scrollX:n,scrollY:l}}e(k.anchorNode.nodeValue||"").replace(/^\s+/,function(r){g=r.length});return{start:Math.max(v.start+k.anchorOffset-g,0),end:Math.max(v.end+k.focusOffset-g,0),scrollX:n,scrollY:l,beg:k.anchorOffset-g==0}}else{v=q(h,m.startContainer,m.endContainer);if(!v){return{scrollX:n,scrollY:l}}return{start:Math.max(v.start+m.startOffset-g,0),end:Math.max(v.end+m.endOffset-i,0),scrollX:n,scrollY:l,beg:m.startOffset-g==0}}},moveToBookmark:function(n){var o=this,g=o.getRng(),p=o.getSel(),j=o.dom.getRoot(),m,h,k;function i(q,t,D){var B=o.dom.doc.createTreeWalker(q,NodeFilter.SHOW_TEXT,null,false),x,s=0,A={},u,C,z,y;while((x=B.nextNode())!=null){z=y=0;k=x.nodeValue||"";h=e(k).length;s+=h;if(s>=t&&!A.startNode){u=t-(s-h);if(n.beg&&u>=h){continue}A.startNode=x;A.startOffset=u+y}if(s>=D){A.endNode=x;A.endOffset=D-(s-h)+y;return A}}return null}if(!n){return false}o.win.scrollTo(n.scrollX,n.scrollY);if(a){if(g=n.rng){try{g.select()}catch(l){}return true}o.win.focus();if(n.tag){g=j.createControlRange();d(o.dom.select(n.tag),function(r,q){if(q==n.index){g.addElement(r)}})}else{try{if(n.start<0){return true}g=p.createRange();g.moveToElementText(j);g.collapse(true);g.moveStart("character",n.start);g.moveEnd("character",n.length)}catch(f){return true}}try{g.select()}catch(l){}return true}if(!p){return false}if(n.rng){p.removeAllRanges();p.addRange(n.rng)}else{if(b(n.start)&&b(n.end)){try{m=i(j,n.start,n.end);if(m){g=o.dom.doc.createRange();g.setStart(m.startNode,m.startOffset);g.setEnd(m.endNode,m.endOffset);p.removeAllRanges();p.addRange(g)}if(!c.isOpera){o.win.focus()}}catch(l){}}}},select:function(g,l){var p=this,f=p.getRng(),q=p.getSel(),o,m,k,j=p.win.document;function h(u,t){var s,r;if(u){s=j.createTreeWalker(u,NodeFilter.SHOW_TEXT,null,false);while(u=s.nextNode()){r=u;if(c.trim(u.nodeValue).length!=0){if(t){return u}else{r=u}}}}return r}if(a){try{o=j.body;if(/^(IMG|TABLE)$/.test(g.nodeName)){f=o.createControlRange();f.addElement(g)}else{f=o.createTextRange();f.moveToElementText(g)}f.select()}catch(i){}}else{if(l){m=h(g,1)||p.dom.select("br:first",g)[0];k=h(g,0)||p.dom.select("br:last",g)[0];if(m&&k){f=j.createRange();if(m.nodeName=="BR"){f.setStartBefore(m)}else{f.setStart(m,0)}if(k.nodeName=="BR"){f.setEndBefore(k)}else{f.setEnd(k,k.nodeValue.length)}}else{f.selectNode(g)}}else{f.selectNode(g)}p.setRng(f)}return g},isCollapsed:function(){var f=this,h=f.getRng(),g=f.getSel();if(!h||h.item){return false}return !g||h.boundingWidth==0||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=a?g.win.document.body.createTextRange():g.win.document.createRange()}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 f=this,h=f.getRng(),g=f.getSel(),i;if(!a){if(!h){return f.dom.getRoot()}i=h.commonAncestorContainer;if(!h.collapsed){if(c.isWebKit&&g.anchorNode&&g.anchorNode.nodeType==1){return g.anchorNode.childNodes[g.anchorOffset]}if(h.startContainer==h.endContainer){if(h.startOffset-h.endOffset<2){if(h.startContainer.hasChildNodes()){i=h.startContainer.childNodes[h.startOffset]}}}}return f.dom.getParent(i,"*")}return h.item?h.item(0):h.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(""+b+">")}if(this.settings.indentation>0){this.writeRaw("\n")}}},writeFullEndElement:function(){if(this.tags.length>0){this._writeAttributesEnd();this.writeRaw(""+this.tags.pop()+">");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",bool_attrs:/(checked|disabled|readonly|selected|nowrap)/,valid_elements:"*[*]",extended_valid_elements:0,valid_child_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,font_size_style_values: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;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""+o+">"}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,y,w=["ol","ul"],u,t,q,k=/^(OL|UL)$/,z;function m(r,x){var o=x.split(","),p;while((r=r.previousSibling)!=null){for(p=0;p1){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]*>)(.*?)(<\/script>)/g},{pattern:/(]*>)(.*?)(<\/noscript>)/g},{pattern:/('; + }); + + // Wrap noscript elements + h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { + return ''; + }); + } + + h = h.replace(//g, ''); + + // Process all tags with src, href or style + h = h.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi, function(a, n) { + function handle(m, b, c) { + var u = c; + + // Tag already got a mce_ version + if (a.indexOf('mce_' + b) != -1) + return m; + + if (b == 'style') { + // No mce_style for elements with these since they might get resized by the user + if (t._isRes(c)) + return m; + + if (s.hex_colors) { + u = u.replace(/rgb\([^\)]+\)/g, function(v) { + return t.toHex(v); + }); + } + + if (s.url_converter) { + u = u.replace(/url\([\'\"]?([^\)\'\"]+)\)/g, function(x, c) { + return 'url(' + t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)) + ')'; + }); + } + } else if (b != 'coords' && b != 'shape') { + if (s.url_converter) + u = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)); + } + + return ' ' + b + '="' + c + '" mce_' + b + '="' + u + '"'; + }; + + a = a.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi, handle); // W3C + a = a.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi, handle); // W3C + + return a.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi, handle); // IE + }); + } + + 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; + + return this.run(e, function(e) { + var n, tp; + + e = t.get(e); + d = d || e.ownerDocument || t.doc; + + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else { + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + } + }); + }, + + decode : function(s) { + var e, n, v; + + // Look for entities to decode + if (/&[^;]+;/.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.nextSibling); + } + + return v || s; + } + + return s; + }, + + encode : function(s) { + return s ? ('' + s).replace(/[<>&\"]/g, function (c, b) { + switch (c) { + case '&': + return '&'; + + case '"': + return '"'; + + case '<': + return '<'; + + case '>': + return '>'; + } + + return c; + }) : s; + }, + + insertAfter : function(n, r) { + var t = this; + + r = t.get(r); + + return this.run(n, function(n) { + var p, ns; + + p = r.parentNode; + ns = r.nextSibling; + + if (ns) + p.insertBefore(n, ns); + else + p.appendChild(n); + + return n; + }); + }, + + isBlock : function(n) { + if (n.nodeType && n.nodeType !== 1) + return false; + + n = n.nodeName || n; + + return /^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.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(o.childNodes, function(c) { + n.appendChild(c.cloneNode(true)); + }); + } + + // Fix IE psuedo leak for elements since replacing elements if fairly common + // Will break parentNode for some unknown reason + if (t.fixPsuedoLeaks && o.nodeType === 1) { + o.parentNode.insertBefore(n, o); + t.remove(o); + return n; + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + 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; + + // 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(/([a-z0-9\:\-_]+)=/gi, function(a, b) { + o.push({specified : 1, nodeName : b}); + }); + + 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); + }, + + 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 sence + // but we don't want that in our code since it serves no purpose + // For example if this is chopped: + // text 1CHOPtext 2 + // would produce: + // text 1CHOPtext 2 + // this function will then trim of empty edges and produce: + // text 1CHOPtext 2 + function trimEdge(n, na) { + n = n[na]; + + if (n && n[na] && n[na].nodeType == 1 && isEmpty(n[na])) + t.remove(n[na]); + }; + + function isEmpty(n) { + n = t.getOuterHTML(n); + n = n.replace(/<(img|hr|table)/gi, '-'); // Keep these convert them to - chars + n = n.replace(/<[^>]+>/g, ''); // Remove all tags + + return n.replace(/[ \t\r\n]+| | /g, '') == ''; + }; + + if (pe && e) { + // Get before chunk + r.setStartBefore(pe); + r.setEndBefore(e); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStartAfter(e); + r.setEndAfter(pe); + aft = r.extractContents(); + + // Insert chunks and remove parent + pa = pe.parentNode; + + // Remove right side edge of the before contents + trimEdge(bef, 'lastChild'); + + if (!isEmpty(bef)) + pa.insertBefore(bef, pe); + + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Remove left site edge of the after contents + trimEdge(aft, 'firstChild'); + + if (!isEmpty(aft)) + pa.insertBefore(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); + }, + + + _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; + } + */ + + }); + + // Setup page DOM + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); +(function(ns) { + // Traverse constants + var EXTRACT = 0, CLONE = 1, DELETE = 2, extend = tinymce.extend; + + function indexOf(child, parent) { + var i, node; + + if (child.parentNode != parent) + return -1; + + for (node = parent.firstChild, i = 0; node != child; node = node.nextSibling) + i++; + + return i; + }; + + function nodeIndex(n) { + var i = 0; + + while (n.previousSibling) { + i++; + n = n.previousSibling; + } + + return i; + }; + + function getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child != null && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child != null) + return child; + + return container; + }; + + // Range constructor + function Range(dom) { + var d = dom.doc; + + extend(this, { + dom : dom, + + // Inital states + startContainer : d, + startOffset : 0, + endContainer : d, + endOffset : 0, + collapsed : true, + commonAncestorContainer : d, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3 + }); + }; + + // Add range methods + extend(Range.prototype, { + setStart : function(n, o) { + this._setEndPoint(true, n, o); + }, + + setEnd : function(n, o) { + this._setEndPoint(false, n, o); + }, + + setStartBefore : function(n) { + this.setStart(n.parentNode, nodeIndex(n)); + }, + + setStartAfter : function(n) { + this.setStart(n.parentNode, nodeIndex(n) + 1); + }, + + setEndBefore : function(n) { + this.setEnd(n.parentNode, nodeIndex(n)); + }, + + setEndAfter : function(n) { + this.setEnd(n.parentNode, nodeIndex(n) + 1); + }, + + collapse : function(ts) { + var t = this; + + if (ts) { + t.endContainer = t.startContainer; + t.endOffset = t.startOffset; + } else { + t.startContainer = t.endContainer; + t.startOffset = t.endOffset; + } + + t.collapsed = true; + }, + + selectNode : function(n) { + this.setStartBefore(n); + this.setEndAfter(n); + }, + + selectNodeContents : function(n) { + this.setStart(n, 0); + this.setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }, + + compareBoundaryPoints : function(h, r) { + var t = this, sc = t.startContainer, so = t.startOffset, ec = t.endContainer, eo = t.endOffset; + + // Check START_TO_START + if (h === 0) + return t._compareBoundaryPoints(sc, so, sc, so); + + // Check START_TO_END + if (h === 1) + return t._compareBoundaryPoints(sc, so, ec, eo); + + // Check END_TO_END + if (h === 2) + return t._compareBoundaryPoints(ec, eo, ec, eo); + + // Check END_TO_START + if (h === 3) + return t._compareBoundaryPoints(ec, eo, sc, so); + }, + + deleteContents : function() { + this._traverse(DELETE); + }, + + extractContents : function() { + return this._traverse(EXTRACT); + }, + + cloneContents : function() { + return this._traverse(CLONE); + }, + + insertNode : function(n) { + var t = this, nn, o; + + // Node is TEXT_NODE or CDATA + if (n.nodeType === 3 || n.nodeType === 4) { + nn = t.startContainer.splitText(t.startOffset); + t.startContainer.parentNode.insertBefore(n, nn); + } else { + // Insert element node + if (t.startContainer.childNodes.length > 0) + o = t.startContainer.childNodes[t.startOffset]; + + t.startContainer.insertBefore(n, o); + } + }, + + surroundContents : function(n) { + var t = this, f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }, + + cloneRange : function() { + var t = this; + + return extend(new Range(t.dom), { + startContainer : t.startContainer, + startOffset : t.startOffset, + endContainer : t.endContainer, + endOffset : t.endOffset, + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }, + +/* + detach : function() { + // Not implemented + }, +*/ + // Internal methods + + _isCollapsed : function() { + return (this.startContainer == this.endContainer && this.startOffset == this.endOffset); + }, + + _compareBoundaryPoints : function (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 + } else if (offsetA < offsetB) { + return -1; // before + } else { + 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 + } else { + 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 + } else { + 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 = this.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; + } + }, + + _setEndPoint : function(st, n, o) { + var t = this, ec, sc; + + if (st) { + t.startContainer = n; + t.startOffset = o; + } else { + t.endContainer = n; + t.endOffset = 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.endContainer; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t.startContainer; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc != ec) { + t.collapse(st); + } else { + // 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 (t._compareBoundaryPoints(t.startContainer, t.startOffset, t.endContainer, t.endOffset) > 0) + t.collapse(st); + } + + t.collapsed = t._isCollapsed(); + t.commonAncestorContainer = t.dom.findCommonAncestor(t.startContainer, t.endContainer); + }, + + // This code is heavily "inspired" by the Apache Xerces implementation. I hope they don't mind. :) + + _traverse : function(how) { + var t = this, c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t.startContainer == t.endContainer) + return t._traverseSameContainer(how); + + for (c = t.endContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.startContainer) + return t._traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t.startContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.endContainer) + return t._traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t.startContainer; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t.endContainer; + 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 t._traverseCommonAncestors(startNode, endNode, how); + }, + + _traverseSameContainer : function(how) { + var t = this, frag, s, sub, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t.startOffset == t.endOffset) + return frag; + + // Text node needs special case handling + if (t.startContainer.nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t.startContainer.nodeValue; + sub = s.substring(t.startOffset, t.endOffset); + + // set the original text node to its new value + if (how != CLONE) { + t.startContainer.deleteData(t.startOffset, t.endOffset - t.startOffset); + + // Nothing is partially selected, so collapse to start point + t.collapse(true); + } + + if (how == DELETE) + return null; + + frag.appendChild(t.dom.doc.createTextNode(sub)); + return frag; + } + + // Copy nodes between the start/end offsets. + n = getSelectedNode(t.startContainer, t.startOffset); + cnt = t.endOffset - t.startOffset; + + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._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; + }, + + _traverseCommonStartContainer : function(endAncestor, how) { + var t = this, frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = indexOf(endAncestor, t.startContainer); + cnt = endIdx - t.startOffset; + + 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 = t._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; + }, + + _traverseCommonEndContainer : function(startAncestor, how) { + var t = this, frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = indexOf(startAncestor, t.endContainer); + ++startIdx; // Because we already traversed it.... + + cnt = t.endOffset - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseCommonAncestors : function(startAncestor, endAncestor, how) { + var t = this, n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = indexOf(startAncestor, commonParent); + endOffset = indexOf(endAncestor, commonParent); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = t._traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseRightBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.endContainer, t.endOffset - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent; + var isFullySelected = next != t.endContainer; + + if (next == root) + return t._traverseNode(next, isFullySelected, false, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, false, how); + + while (parent != null) { + while (next != null) { + prevSibling = next.previousSibling; + clonedChild = t._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 = t._traverseNode(parent, false, false, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseLeftBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.startContainer, t.startOffset); + var isFullySelected = next != t.startContainer, parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return t._traverseNode(next, isFullySelected, true, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, true, how); + + while (parent != null) { + while (next != null) { + nextSibling = next.nextSibling; + clonedChild = t._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 = t._traverseNode(parent, false, true, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseNode : function(n, isFullySelected, isLeft, how) { + var t = this, txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return t._traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t.startOffset; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t.endOffset; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return null; + + newNode = n.cloneNode(false); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return null; + + return n.cloneNode(false); + }, + + _traverseFullySelected : function(n, how) { + var t = this; + + if (how != DELETE) + return how == CLONE ? n.cloneNode(true) : n; + + n.parentNode.removeChild(n); + return null; + } + }); + + ns.Range = Range; +})(tinymce.dom); +(function() { + function Selection(selection) { + var t = this, invisibleChar = '\uFEFF', range, lastIERng; + + 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 1; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return 1; + } + + return 0; + }; + + function getRange() { + var dom = selection.dom, ieRange = selection.getRng(), domRange = dom.createRng(), startPos, endPos, element, sc, ec, collapsed; + + function findIndex(element) { + var nl = element.parentNode.childNodes, i; + + for (i = nl.length - 1; i >= 0; i--) { + if (nl[i] == element) + return i; + } + + return -1; + }; + + function findEndPoint(start) { + var rng = ieRange.duplicate(), parent, i, nl, n, offset = 0, index = 0, pos, tmpRng; + + // Insert marker character + rng.collapse(start); + parent = rng.parentElement(); + rng.pasteHTML(invisibleChar); // Needs to be a pasteHTML instead of .text = since IE has a bug with nodeValue + + // Find marker character + nl = parent.childNodes; + for (i = 0; i < nl.length; i++) { + n = nl[i]; + + // Calculate node index excluding text node fragmentation + if (i > 0 && (n.nodeType !== 3 || nl[i - 1].nodeType !== 3)) + index++; + + // If text node then calculate offset + if (n.nodeType === 3) { + // Look for marker + pos = n.nodeValue.indexOf(invisibleChar); + if (pos !== -1) { + offset += pos; + break; + } + + offset += n.nodeValue.length; + } else + offset = 0; + } + + // Remove marker character + rng.moveStart('character', -1); + rng.text = ''; + + return {index : index, offset : offset, parent : parent}; + }; + + // 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, findIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + // Check collapsed state + collapsed = selection.isCollapsed(); + + // Find start and end pos index and offset + startPos = findEndPoint(true); + endPos = findEndPoint(false); + + // Normalize the elements to avoid fragmented dom + startPos.parent.normalize(); + endPos.parent.normalize(); + + // Set start container and offset + sc = startPos.parent.childNodes[Math.min(startPos.index, startPos.parent.childNodes.length - 1)]; + + if (sc.nodeType != 3) + domRange.setStart(startPos.parent, startPos.index); + else + domRange.setStart(startPos.parent.childNodes[startPos.index], startPos.offset); + + // Set end container and offset + ec = endPos.parent.childNodes[Math.min(endPos.index, endPos.parent.childNodes.length - 1)]; + + if (ec.nodeType != 3) { + if (!collapsed) + endPos.index++; + + domRange.setEnd(endPos.parent, endPos.index); + } else + domRange.setEnd(endPos.parent.childNodes[endPos.index], endPos.offset); + + // If not collapsed then make sure offsets are valid + if (!collapsed) { + sc = domRange.startContainer; + if (sc.nodeType == 1) + domRange.setStart(sc, Math.min(domRange.startOffset, sc.childNodes.length)); + + ec = domRange.endContainer; + if (ec.nodeType == 1) + domRange.setEnd(ec, Math.min(domRange.endOffset, ec.childNodes.length)); + } + + // Restore selection to new range + t.addRange(domRange); + + return domRange; + }; + + this.addRange = function(rng) { + var ieRng, body = selection.dom.doc.body, startPos, endPos, sc, so, ec, eo; + + // Setup some shorter versions + sc = rng.startContainer; + so = rng.startOffset; + ec = rng.endContainer; + eo = rng.endOffset; + ieRng = body.createTextRange(); + + // Find element + sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc; + ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec; + + // 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(); + + return; + } + + function getCharPos(container, offset) { + var nodeVal, rng, pos; + + if (container.nodeType != 3) + return -1; + + nodeVal = container.nodeValue; + rng = body.createTextRange(); + + // Insert marker at offset position + container.nodeValue = nodeVal.substring(0, offset) + invisibleChar + nodeVal.substring(offset); + + // Find char pos of marker and remove it + rng.moveToElementText(container.parentNode); + rng.findText(invisibleChar); + pos = Math.abs(rng.moveStart('character', -0xFFFFF)); + container.nodeValue = nodeVal; + + return pos; + }; + + // Collapsed range + if (rng.collapsed) { + pos = getCharPos(sc, so); + + ieRng = body.createTextRange(); + ieRng.move('character', pos); + ieRng.select(); + + return; + } else { + // If same text container + if (sc == ec && sc.nodeType == 3) { + startPos = getCharPos(sc, so); + + ieRng.move('character', startPos); + ieRng.moveEnd('character', eo - so); + ieRng.select(); + + return; + } + + // Get caret positions + startPos = getCharPos(sc, so); + endPos = getCharPos(ec, eo); + ieRng = body.createTextRange(); + + // Move start of range to start character position or start element + if (startPos == -1) { + ieRng.moveToElementText(sc); + startPos = 0; + } else + ieRng.move('character', startPos); + + // Move end of range to end character position or end element + tmpRng = body.createTextRange(); + + if (endPos == -1) + tmpRng.moveToElementText(ec); + else + tmpRng.move('character', endPos); + + ieRng.setEndPoint('EndToEnd', tmpRng); + ieRng.select(); + + return; + } + }; + + 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; + }; + }; + + // 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 + // 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; + } + } + + }); + + // Shorten name and setup global instance + 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) { + var each = tinymce.each; + + tinymce.create('tinymce.dom.Element', { + Element : function(id, s) { + var t = this, dom, el; + + s = s || {}; + t.id = id; + t.dom = dom = s.dom || tinymce.DOM; + t.settings = s; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = t.dom.get(t.id); + + each([ + 'getPos', + 'getRect', + 'getParent', + 'add', + 'setStyle', + 'getStyle', + 'setStyles', + 'setAttrib', + 'setAttribs', + 'getAttrib', + 'addClass', + 'removeClass', + 'hasClass', + 'getOuterHTML', + 'setOuterHTML', + 'remove', + 'show', + 'hide', + 'isHidden', + 'setHTML', + 'get' + ], 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; + }; + }); + }, + + on : function(n, f, s) { + return tinymce.dom.Event.add(this.id, n, f, s); + }, + + getXY : function() { + return { + x : parseInt(this.getStyle('left')), + y : parseInt(this.getStyle('top')) + }; + }, + + getSize : function() { + var n = this.dom.get(this.id); + + return { + w : parseInt(this.getStyle('width') || n.clientWidth), + h : parseInt(this.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + this.setStyles({left : x, top : y}); + }, + + moveBy : function(x, y) { + var p = this.getXY(); + + this.moveTo(p.x + x, p.y + y); + }, + + resizeTo : function(w, h) { + this.setStyles({width : w, height : h}); + }, + + resizeBy : function(w, h) { + var s = this.getSize(); + + this.resizeTo(s.w + w, s.h + h); + }, + + update : function(k) { + var t = this, b, dom = t.dom; + + if (tinymce.isIE6 && t.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(t.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.setStyle(b, 'left', t.getStyle('left', 1)); + dom.setStyle(b, 'top', t.getStyle('top', 1)); + dom.setStyle(b, 'width', t.getStyle('width', 1)); + dom.setStyle(b, 'height', t.getStyle('height', 1)); + dom.setStyle(b, 'display', t.getStyle('display', 1)); + dom.setStyle(b, '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 + 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.setEndAfter(c); + t.setRng(r); + + // Delete the marker, and hopefully the caret gets placed in the right location + // Removed this since it seems to remove in FF and simply deleting it + // doesn't seem to affect the caret position in any browser + //d.execCommand('Delete', false, null); + + // 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 (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(1); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.firstChild; + + return e; + } else { + e = r.startContainer; + + if (e.nodeName == 'BODY') + return e.firstChild; + + return t.dom.getParent(e, '*'); + } + }, + + getEnd : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild; + + return e; + } else { + e = r.endContainer; + + if (e.nodeName == 'BODY') + return e.lastChild; + + return t.dom.getParent(e, '*'); + } + }, + + getBookmark : function(si) { + var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv; + sx = vp.x; + sy = vp.y; + + // Simple bookmark fast but not as persistent + if (si) + return {rng : r, scrollX : sx, scrollY : sy}; + + // Handle IE + if (isIE) { + // Control selection + if (r.item) { + e = r.item(0); + + each(t.dom.select(e.nodeName), function(n, i) { + if (e == n) { + sp = i; + return false; + } + }); + + return { + tag : e.nodeName, + index : sp, + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + tr = t.dom.doc.body.createTextRange(); + tr.moveToElementText(ro); + tr.collapse(true); + bp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(true); + sp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(false); + le = Math.abs(tr.move('character', c)) - sp; + + return { + start : sp - bp, + length : le, + scrollX : sx, + scrollY : sy + }; + } + + // Handle W3C + e = t.getNode(); + s = t.getSel(); + + if (!s) + return null; + + // Image selection + if (e && e.nodeName == 'IMG') { + return { + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + + function getPos(r, sn, en) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; + + while ((n = w.nextNode()) != null) { + if (n == sn) + d.start = p; + + if (n == en) { + d.end = p; + return d; + } + + p += trimNl(n.nodeValue || '').length; + } + + return null; + }; + + // Caret or selection + if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) { + e = getPos(ro, s.anchorNode, s.focusNode); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + // Count whitespace before + trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + + return { + start : Math.max(e.start + s.anchorOffset - wb, 0), + end : Math.max(e.end + s.focusOffset - wb, 0), + scrollX : sx, + scrollY : sy, + beg : s.anchorOffset - wb == 0 + }; + } else { + e = getPos(ro, r.startContainer, r.endContainer); + + // Count whitespace before start and end container + //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;}); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + return { + start : Math.max(e.start + r.startOffset - wb, 0), + end : Math.max(e.end + r.endOffset - wa, 0), + scrollX : sx, + scrollY : sy, + beg : r.startOffset - wb == 0 + }; + } + }, + + moveToBookmark : function(b) { + var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv; + + function getPos(r, sp, ep) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb; + + while ((n = w.nextNode()) != null) { + wa = wb = 0; + + nv = n.nodeValue || ''; + //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;}); + //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;}); + + nvl = trimNl(nv).length; + p += nvl; + + if (p >= sp && !d.startNode) { + o = sp - (p - nvl); + + // Fix for odd quirk in FF + if (b.beg && o >= nvl) + continue; + + d.startNode = n; + d.startOffset = o + wb; + } + + if (p >= ep) { + d.endNode = n; + d.endOffset = ep - (p - nvl) + wb; + return d; + } + } + + return null; + }; + + if (!b) + return false; + + t.win.scrollTo(b.scrollX, b.scrollY); + + // Handle explorer + if (isIE) { + // Handle simple + if (r = b.rng) { + try { + r.select(); + } catch (ex) { + // Ignore + } + + return true; + } + + t.win.focus(); + + // Handle control bookmark + if (b.tag) { + r = ro.createControlRange(); + + each(t.dom.select(b.tag), function(n, i) { + if (i == b.index) + r.addElement(n); + }); + } else { + // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs + try { + // Incorrect bookmark + if (b.start < 0) + return true; + + r = s.createRange(); + r.moveToElementText(ro); + r.collapse(true); + r.moveStart('character', b.start); + r.moveEnd('character', b.length); + } catch (ex2) { + return true; + } + } + + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + + return true; + } + + // Handle W3C + if (!s) + return false; + + // Handle simple + if (b.rng) { + s.removeAllRanges(); + s.addRange(b.rng); + } else { + if (is(b.start) && is(b.end)) { + try { + sd = getPos(ro, b.start, b.end); + + if (sd) { + r = t.dom.doc.createRange(); + r.setStart(sd.startNode, sd.startOffset); + r.setEnd(sd.endNode, sd.endOffset); + s.removeAllRanges(); + s.addRange(r); + } + + if (!tinymce.isOpera) + t.win.focus(); + } catch (ex) { + // Ignore + } + } + } + }, + + select : function(n, c) { + var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document; + + function find(n, start) { + var walker, o; + + if (n) { + walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + // Find first/last non empty text node + while (n = walker.nextNode()) { + o = n; + + if (tinymce.trim(n.nodeValue).length != 0) { + if (start) + return n; + else + o = n; + } + } + } + + return o; + }; + + if (isIE) { + try { + b = d.body; + + if (/^(IMG|TABLE)$/.test(n.nodeName)) { + r = b.createControlRange(); + r.addElement(n); + } else { + r = b.createTextRange(); + r.moveToElementText(n); + } + + r.select(); + } catch (ex) { + // Throws illigal agrument in IE some times + } + } else { + if (c) { + fn = find(n, 1) || t.dom.select('br:first', n)[0]; + ln = find(n, 0) || t.dom.select('br:last', n)[0]; + + if (fn && ln) { + r = d.createRange(); + + if (fn.nodeName == 'BR') + r.setStartBefore(fn); + else + r.setStart(fn, 0); + + if (ln.nodeName == 'BR') + r.setEndBefore(ln); + else + r.setEnd(ln, ln.nodeValue.length); + } else + r.selectNode(n); + } else + r.selectNode(n); + + t.setRng(r); + } + + return n; + }, + + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + return !s || r.boundingWidth == 0 || 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 = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange(); + + 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, r = t.getRng(), s = t.getSel(), e; + + if (!isIE) { + // Range maybe lost after the editor is made visible again + if (!r) + return t.dom.getRoot(); + + e = r.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!r.collapsed) { + // If the anchor node is a element instead of a text node then return this element + if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1) + return s.anchorNode.childNodes[s.anchorOffset]; + + if (r.startContainer == r.endContainer) { + if (r.startOffset - r.endOffset < 2) { + if (r.startContainer.hasChildNodes()) + e = r.startContainer.childNodes[r.startOffset]; + } + } + } + + return t.dom.getParent(e, '*'); + } + + return r.item ? r.item(0) : r.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('' + n + '>'); + + if (this.settings.indentation > 0) + this.writeRaw('\n'); + } + }, + + writeFullEndElement : function() { + if (this.tags.length > 0) { + this._writeAttributesEnd(); + this.writeRaw('' + this.tags.pop() + '>'); + + 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', + bool_attrs : /(checked|disabled|readonly|selected|nowrap)/, + valid_elements : '*[*]', + extended_valid_elements : 0, + valid_child_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, + font_size_style_values : 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; + + 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 '' + c + '>'; + + 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 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 ]*>)(.*?)(<\/script>)/g}, + {pattern : /(]*>)(.*?)(<\/noscript>)/g}, + {pattern : /("});j=j.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(h,l,k){return""})}j=j.replace(//g,"");j=j.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi,function(h,l){function k(o,n,q){var p=q;if(h.indexOf("mce_"+n)!=-1){return o}if(n=="style"){if(g._isRes(q)){return o}if(i.hex_colors){p=p.replace(/rgb\([^\)]+\)/g,function(m){return g.toHex(m)})}if(i.url_converter){p=p.replace(/url\([\'\"]?([^\)\'\"]+)\)/g,function(m,r){return"url("+g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(r),n,l))+")"})}}else{if(n!="coords"&&n!="shape"){if(i.url_converter){p=g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(q),n,l))}}}return" "+n+'="'+q+'" mce_'+n+'="'+p+'"'}h=h.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi,k);h=h.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi,k);return h.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi,k)})}return j},getOuterHTML:function(f){var g;f=this.get(f);if(!f){return null}if(f.outerHTML!==undefined){return f.outerHTML}g=(f.ownerDocument||this.doc).createElement("body");g.appendChild(f.cloneNode(true));return g.innerHTML},setOuterHTML:function(i,g,j){var f=this;return this.run(i,function(h){var l,k;h=f.get(h);j=j||h.ownerDocument||f.doc;if(a&&h.nodeType==1){h.outerHTML=g}else{k=j.createElement("body");k.innerHTML=g;l=k.lastChild;while(l){f.insertAfter(l.cloneNode(true),h);l=l.previousSibling}f.remove(h)}})},decode:function(g){var h,i,f;if(/&[^;]+;/.test(g)){h=this.doc.createElement("div");h.innerHTML=g;i=h.firstChild;f="";if(i){do{f+=i.nodeValue}while(i.nextSibling)}return f||g}return g},encode:function(f){return f?(""+f).replace(/[<>&\"]/g,function(h,g){switch(h){case"&":return"&";case'"':return""";case"<":return"<";case">":return">"}return h}):f},insertAfter:function(h,g){var f=this;g=f.get(g);return this.run(h,function(k){var j,i;j=g.parentNode;i=g.nextSibling;if(i){j.insertBefore(k,i)}else{j.appendChild(k)}return k})},isBlock:function(f){if(f.nodeType&&f.nodeType!==1){return false}f=f.nodeName||f;return/^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(f)},replace:function(i,h,f){var g=this;if(b(h,"array")){i=i.cloneNode(true)}return g.run(h,function(j){if(f){e(j.childNodes,function(k){i.appendChild(k.cloneNode(true))})}if(g.fixPsuedoLeaks&&j.nodeType===1){j.parentNode.insertBefore(i,j);g.remove(j);return i}return j.parentNode.replaceChild(i,j)})},findCommonAncestor:function(h,f){var i=h,g;while(i){g=f;while(g&&i!=g){g=g.parentNode}if(i==g){break}i=i.parentNode}if(!i&&h.ownerDocument){return h.ownerDocument.documentElement}return i},toHex:function(f){var h=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(f);function g(i){i=parseInt(i).toString(16);return i.length>1?i:"0"+i}if(h){f="#"+g(h[1])+g(h[2])+g(h[3]);return f}return f},getClasses:function(){var l=this,g=[],k,m={},n=l.settings.class_filter,j;if(l.classes){return l.classes}function o(f){e(f.imports,function(i){o(i)});e(f.cssRules||f.rules,function(i){switch(i.type||1){case 1:if(i.selectorText){e(i.selectorText.split(","),function(p){p=p.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(p)||!/\.[\w\-]+$/.test(p)){return}j=p;p=p.replace(/.*\.([a-z0-9_\-]+).*/i,"$1");if(n&&!(p=n(p,j))){return}if(!m[p]){g.push({"class":p});m[p]=1}})}break;case 3:o(i.styleSheet);break}})}try{e(l.doc.styleSheets,o)}catch(h){}if(g.length>0){l.classes=g}return g},run:function(j,i,h){var g=this,k;if(g.doc&&typeof(j)==="string"){j=g.get(j)}if(!j){return false}h=h||this;if(!j.nodeType&&(j.length||j.length===0)){k=[];e(j,function(l,f){if(l){if(typeof(l)=="string"){l=g.doc.getElementById(l)}k.push(i.call(h,l,f))}});return k}return i.call(h,j)},getAttribs:function(g){var f;g=this.get(g);if(!g){return[]}if(a){f=[];if(g.nodeName=="OBJECT"){return g.attributes}g.cloneNode(false).outerHTML.replace(/([a-z0-9\:\-_]+)=/gi,function(i,h){f.push({specified:1,nodeName:h})});return f}return g.attributes},destroy:function(g){var f=this;if(f.events){f.events.destroy()}f.win=f.doc=f.root=f.events=null;if(!g){c.removeUnload(f.destroy)}},createRng:function(){var f=this.doc;return f.createRange?f.createRange():new c.dom.Range(this)},split:function(k,j,n){var o=this,f=o.createRng(),l,i,m;function g(q,p){q=q[p];if(q&&q[p]&&q[p].nodeType==1&&h(q[p])){o.remove(q[p])}}function h(p){p=o.getOuterHTML(p);p=p.replace(/<(img|hr|table)/gi,"-");p=p.replace(/<[^>]+>/g,"");return p.replace(/[ \t\r\n]+| | /g,"")==""}if(k&&j){f.setStartBefore(k);f.setEndBefore(j);l=f.extractContents();f=o.createRng();f.setStartAfter(j);f.setEndAfter(k);i=f.extractContents();m=k.parentNode;g(l,"lastChild");if(!h(l)){m.insertBefore(l,k)}if(n){m.replaceChild(n,j)}else{m.insertBefore(j,k)}g(i,"firstChild");if(!h(i)){m.insertBefore(i,k)}o.remove(k);return n||j}},bind:function(j,f,i,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.add(j,f,i,h||this)},unbind:function(i,f,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.remove(i,f,h)},_isRes:function(f){return/^(top|left|bottom|right|width|height)/i.test(f)||/;\s*(top|left|bottom|right|width|height)/i.test(f)}});c.DOM=new c.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(f){var h=0,c=1,e=2,d=tinymce.extend;function g(m,k){var j,l;if(m.parentNode!=k){return -1}for(l=k.firstChild,j=0;l!=m;l=l.nextSibling){j++}return j}function b(k){var j=0;while(k.previousSibling){j++;k=k.previousSibling}return j}function i(j,k){var l;if(j.nodeType==3){return j}if(k<0){return j}l=j.firstChild;while(l!=null&&k>0){--k;l=l.nextSibling}if(l!=null){return l}return j}function a(k){var j=k.doc;d(this,{dom:k,startContainer:j,startOffset:0,endContainer:j,endOffset:0,collapsed:true,commonAncestorContainer:j,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3})}d(a.prototype,{setStart:function(k,j){this._setEndPoint(true,k,j)},setEnd:function(k,j){this._setEndPoint(false,k,j)},setStartBefore:function(j){this.setStart(j.parentNode,b(j))},setStartAfter:function(j){this.setStart(j.parentNode,b(j)+1)},setEndBefore:function(j){this.setEnd(j.parentNode,b(j))},setEndAfter:function(j){this.setEnd(j.parentNode,b(j)+1)},collapse:function(k){var j=this;if(k){j.endContainer=j.startContainer;j.endOffset=j.startOffset}else{j.startContainer=j.endContainer;j.startOffset=j.endOffset}j.collapsed=true},selectNode:function(j){this.setStartBefore(j);this.setEndAfter(j)},selectNodeContents:function(j){this.setStart(j,0);this.setEnd(j,j.nodeType===1?j.childNodes.length:j.nodeValue.length)},compareBoundaryPoints:function(m,n){var l=this,p=l.startContainer,o=l.startOffset,k=l.endContainer,j=l.endOffset;if(m===0){return l._compareBoundaryPoints(p,o,p,o)}if(m===1){return l._compareBoundaryPoints(p,o,k,j)}if(m===2){return l._compareBoundaryPoints(k,j,k,j)}if(m===3){return l._compareBoundaryPoints(k,j,p,o)}},deleteContents:function(){this._traverse(e)},extractContents:function(){return this._traverse(h)},cloneContents:function(){return this._traverse(c)},insertNode:function(m){var j=this,l,k;if(m.nodeType===3||m.nodeType===4){l=j.startContainer.splitText(j.startOffset);j.startContainer.parentNode.insertBefore(m,l)}else{if(j.startContainer.childNodes.length>0){k=j.startContainer.childNodes[j.startOffset]}j.startContainer.insertBefore(m,k)}},surroundContents:function(l){var j=this,k=j.extractContents();j.insertNode(l);l.appendChild(k);j.selectNode(l)},cloneRange:function(){var j=this;return d(new a(j.dom),{startContainer:j.startContainer,startOffset:j.startOffset,endContainer:j.endContainer,endOffset:j.endOffset,collapsed:j.collapsed,commonAncestorContainer:j.commonAncestorContainer})},_isCollapsed:function(){return(this.startContainer==this.endContainer&&this.startOffset==this.endOffset)},_compareBoundaryPoints:function(m,p,k,o){var q,l,j,r,t,s;if(m==k){if(p==o){return 0}else{if(p0){l.collapse(k)}}l.collapsed=l._isCollapsed();l.commonAncestorContainer=l.dom.findCommonAncestor(l.startContainer,l.endContainer)},_traverse:function(r){var s=this,q,m=0,v=0,k,o,l,n,j,u;if(s.startContainer==s.endContainer){return s._traverseSameContainer(r)}for(q=s.endContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.startContainer){return s._traverseCommonStartContainer(q,r)}++m}for(q=s.startContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.endContainer){return s._traverseCommonEndContainer(q,r)}++v}o=v-m;l=s.startContainer;while(o>0){l=l.parentNode;o--}n=s.endContainer;while(o<0){n=n.parentNode;o++}for(j=l.parentNode,u=n.parentNode;j!=u;j=j.parentNode,u=u.parentNode){l=j;n=u}return s._traverseCommonAncestors(l,n,r)},_traverseSameContainer:function(o){var r=this,q,u,j,k,l,p,m;if(o!=e){q=r.dom.doc.createDocumentFragment()}if(r.startOffset==r.endOffset){return q}if(r.startContainer.nodeType==3){u=r.startContainer.nodeValue;j=u.substring(r.startOffset,r.endOffset);if(o!=c){r.startContainer.deleteData(r.startOffset,r.endOffset-r.startOffset);r.collapse(true)}if(o==e){return null}q.appendChild(r.dom.doc.createTextNode(j));return q}k=i(r.startContainer,r.startOffset);l=r.endOffset-r.startOffset;while(l>0){p=k.nextSibling;m=r._traverseFullySelected(k,o);if(q){q.appendChild(m)}--l;k=p}if(o!=c){r.collapse(true)}return q},_traverseCommonStartContainer:function(j,p){var s=this,r,k,l,m,q,o;if(p!=e){r=s.dom.doc.createDocumentFragment()}k=s._traverseRightBoundary(j,p);if(r){r.appendChild(k)}l=g(j,s.startContainer);m=l-s.startOffset;if(m<=0){if(p!=c){s.setEndBefore(j);s.collapse(false)}return r}k=j.previousSibling;while(m>0){q=k.previousSibling;o=s._traverseFullySelected(k,p);if(r){r.insertBefore(o,r.firstChild)}--m;k=q}if(p!=c){s.setEndBefore(j);s.collapse(false)}return r},_traverseCommonEndContainer:function(m,p){var s=this,r,o,j,k,q,l;if(p!=e){r=s.dom.doc.createDocumentFragment()}j=s._traverseLeftBoundary(m,p);if(r){r.appendChild(j)}o=g(m,s.endContainer);++o;k=s.endOffset-o;j=m.nextSibling;while(k>0){q=j.nextSibling;l=s._traverseFullySelected(j,p);if(r){r.appendChild(l)}--k;j=q}if(p!=c){s.setStartAfter(m);s.collapse(true)}return r},_traverseCommonAncestors:function(p,j,s){var w=this,l,v,o,q,r,k,u,m;if(s!=e){v=w.dom.doc.createDocumentFragment()}l=w._traverseLeftBoundary(p,s);if(v){v.appendChild(l)}o=p.parentNode;q=g(p,o);r=g(j,o);++q;k=r-q;u=p.nextSibling;while(k>0){m=u.nextSibling;l=w._traverseFullySelected(u,s);if(v){v.appendChild(l)}u=m;--k}l=w._traverseRightBoundary(j,s);if(v){v.appendChild(l)}if(s!=c){w.setStartAfter(p);w.collapse(true)}return v},_traverseRightBoundary:function(p,q){var s=this,l=i(s.endContainer,s.endOffset-1),r,o,n,j,k;var m=l!=s.endContainer;if(l==p){return s._traverseNode(l,m,false,q)}r=l.parentNode;o=s._traverseNode(r,false,false,q);while(r!=null){while(l!=null){n=l.previousSibling;j=s._traverseNode(l,m,false,q);if(q!=e){o.insertBefore(j,o.firstChild)}m=true;l=n}if(r==p){return o}l=r.previousSibling;r=r.parentNode;k=s._traverseNode(r,false,false,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseLeftBoundary:function(p,q){var s=this,m=i(s.startContainer,s.startOffset);var n=m!=s.startContainer,r,o,l,j,k;if(m==p){return s._traverseNode(m,n,true,q)}r=m.parentNode;o=s._traverseNode(r,false,true,q);while(r!=null){while(m!=null){l=m.nextSibling;j=s._traverseNode(m,n,true,q);if(q!=e){o.appendChild(j)}n=true;m=l}if(r==p){return o}m=r.nextSibling;r=r.parentNode;k=s._traverseNode(r,false,true,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseNode:function(j,o,r,s){var u=this,m,l,p,k,q;if(o){return u._traverseFullySelected(j,s)}if(j.nodeType==3){m=j.nodeValue;if(r){k=u.startOffset;l=m.substring(k);p=m.substring(0,k)}else{k=u.endOffset;l=m.substring(0,k);p=m.substring(k)}if(s!=c){j.nodeValue=p}if(s==e){return null}q=j.cloneNode(false);q.nodeValue=l;return q}if(s==e){return null}return j.cloneNode(false)},_traverseFullySelected:function(l,k){var j=this;if(k!=e){return k==c?l.cloneNode(true):l}l.parentNode.removeChild(l);return null}});f.Range=a})(tinymce.dom);(function(){function a(e){var d=this,h="\uFEFF",b,g;function c(j,i){if(j&&i){if(j.item&&i.item&&j.item(0)===i.item(0)){return 1}if(j.isEqual&&i.isEqual&&i.isEqual(j)){return 1}}return 0}function f(){var m=e.dom,j=e.getRng(),s=m.createRng(),p,k,n,q,o,l;function i(v){var t=v.parentNode.childNodes,u;for(u=t.length-1;u>=0;u--){if(t[u]==v){return u}}return -1}function r(v){var t=j.duplicate(),B,y,u,w,x=0,z=0,A,C;t.collapse(v);B=t.parentElement();t.pasteHTML(h);u=B.childNodes;for(y=0;y0&&(w.nodeType!==3||u[y-1].nodeType!==3)){z++}if(w.nodeType===3){A=w.nodeValue.indexOf(h);if(A!==-1){x+=A;break}x+=w.nodeValue.length}else{x=0}}t.moveStart("character",-1);t.text="";return{index:z,offset:x,parent:B}}n=j.item?j.item(0):j.parentElement();if(n.ownerDocument!=m.doc){return s}if(j.item||!n.hasChildNodes()){s.setStart(n.parentNode,i(n));s.setEnd(s.startContainer,s.startOffset+1);return s}l=e.isCollapsed();p=r(true);k=r(false);p.parent.normalize();k.parent.normalize();q=p.parent.childNodes[Math.min(p.index,p.parent.childNodes.length-1)];if(q.nodeType!=3){s.setStart(p.parent,p.index)}else{s.setStart(p.parent.childNodes[p.index],p.offset)}o=k.parent.childNodes[Math.min(k.index,k.parent.childNodes.length-1)];if(o.nodeType!=3){if(!l){k.index++}s.setEnd(k.parent,k.index)}else{s.setEnd(k.parent.childNodes[k.index],k.offset)}if(!l){q=s.startContainer;if(q.nodeType==1){s.setStart(q,Math.min(s.startOffset,q.childNodes.length))}o=s.endContainer;if(o.nodeType==1){s.setEnd(o,Math.min(s.endOffset,o.childNodes.length))}}d.addRange(s);return s}this.addRange=function(j){var o,m=e.dom.doc.body,p,k,q,l,n,i;q=j.startContainer;l=j.startOffset;n=j.endContainer;i=j.endOffset;o=m.createTextRange();q=q.nodeType==1?q.childNodes[Math.min(l,q.childNodes.length-1)]:q;n=n.nodeType==1?n.childNodes[Math.min(l==i?i:i-1,n.childNodes.length-1)]:n;if(q==n&&q.nodeType==1){if(/^(IMG|TABLE)$/.test(q.nodeName)&&l!=i){o=m.createControlRange();o.addElement(q)}else{o=m.createTextRange();if(!q.hasChildNodes()&&q.canHaveHTML){q.innerHTML=h}o.moveToElementText(q);if(q.innerHTML==h){o.collapse(true);q.removeChild(q.firstChild)}}if(l==i){o.collapse(i<=j.endContainer.childNodes.length-1)}o.select();return}function r(t,v){var u,s,w;if(t.nodeType!=3){return -1}u=t.nodeValue;s=m.createTextRange();t.nodeValue=u.substring(0,v)+h+u.substring(v);s.moveToElementText(t.parentNode);s.findText(h);w=Math.abs(s.moveStart("character",-1048575));t.nodeValue=u;return w}if(j.collapsed){pos=r(q,l);o=m.createTextRange();o.move("character",pos);o.select();return}else{if(q==n&&q.nodeType==3){p=r(q,l);o.move("character",p);o.moveEnd("character",i-l);o.select();return}p=r(q,l);k=r(n,i);o=m.createTextRange();if(p==-1){o.moveToElementText(q);p=0}else{o.move("character",p)}tmpRng=m.createTextRange();if(k==-1){tmpRng.moveToElementText(n)}else{tmpRng.move("character",k)}o.setEndPoint("EndToEnd",tmpRng);o.select();return}};this.getRangeAt=function(){if(!b||!c(g,e.getRng())){b=f();g=e.getRng()}return b};this.destroy=function(){g=b=null}}tinymce.dom.TridentSelection=a})();(function(){var p=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,i=0,d=Object.prototype.toString,n=false;var b=function(D,t,A,v){A=A||[];var e=t=t||document;if(t.nodeType!==1&&t.nodeType!==9){return[]}if(!D||typeof D!=="string"){return A}var B=[],C,y,G,F,z,s,r=true,w=o(t);p.lastIndex=0;while((C=p.exec(D))!==null){B.push(C[1]);if(C[2]){s=RegExp.rightContext;break}}if(B.length>1&&j.exec(D)){if(B.length===2&&f.relative[B[0]]){y=g(B[0]+B[1],t)}else{y=f.relative[B[0]]?[t]:b(B.shift(),t);while(B.length){D=B.shift();if(f.relative[D]){D+=B.shift()}y=g(D,y)}}}else{if(!v&&B.length>1&&t.nodeType===9&&!w&&f.match.ID.test(B[0])&&!f.match.ID.test(B[B.length-1])){var H=b.find(B.shift(),t,w);t=H.expr?b.filter(H.expr,H.set)[0]:H.set[0]}if(t){var H=v?{expr:B.pop(),set:a(v)}:b.find(B.pop(),B.length===1&&(B[0]==="~"||B[0]==="+")&&t.parentNode?t.parentNode:t,w);y=H.expr?b.filter(H.expr,H.set):H.set;if(B.length>0){G=a(y)}else{r=false}while(B.length){var u=B.pop(),x=u;if(!f.relative[u]){u=""}else{x=B.pop()}if(x==null){x=t}f.relative[u](G,x,w)}}else{G=B=[]}}if(!G){G=y}if(!G){throw"Syntax error, unrecognized expression: "+(u||D)}if(d.call(G)==="[object Array]"){if(!r){A.push.apply(A,G)}else{if(t&&t.nodeType===1){for(var E=0;G[E]!=null;E++){if(G[E]&&(G[E]===true||G[E].nodeType===1&&h(t,G[E]))){A.push(y[E])}}}else{for(var E=0;G[E]!=null;E++){if(G[E]&&G[E].nodeType===1){A.push(y[E])}}}}}else{a(G,A)}if(s){b(s,e,A,v);b.uniqueSort(A)}return A};b.uniqueSort=function(r){if(c){n=false;r.sort(c);if(n){for(var e=1;e":function(w,r,x){var u=typeof r==="string";if(u&&!/\W/.test(r)){r=x?r:r.toUpperCase();for(var s=0,e=w.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,w){var t=u[1].replace(/\\/g,"");if(!w&&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(w,s,t,x){var r=s[1],u=f.filters[r];if(u){return u(w,t,s,x)}else{if(r==="contains"){return(w.textContent||w.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),w=e+"",u=t[2],r=t[4];return e==null?u==="!=":u==="="?w===r:u==="*="?w.indexOf(r)>=0:u==="~="?(" "+w+" ").indexOf(r)>=0:!r?w&&e!==false:u==="!="?w!=r:u==="^="?w.indexOf(r)===0:u==="$="?w.substr(w.length-r.length)===r:u==="|="?w===r||w.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,w){if(typeof v.getElementById!=="undefined"&&!w){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(w,v,t,u){v=v||document;if(!u&&v.nodeType===9&&!o(v)){try{return a(v.querySelectorAll(w),t)}catch(x){}}return e(w,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,w,v,A,x,z){var y=r=="previousSibling"&&!z;for(var t=0,s=A.length;t0){u=e;break}}}e=e[r]}A[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,x){var t=[],u="",v,s=x.nodeType?[x]:x;while((v=f.match.PSEUDO.exec(e))){u+=v[0];e=e.replace(f.match.PSEUDO,"")}e=f.relative[e]?e+"*":e;for(var w=0,r=s.length;w=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){var b=a.each;a.create("tinymce.dom.Element",{Element:function(g,e){var c=this,f,d;e=e||{};c.id=g;c.dom=f=e.dom||a.DOM;c.settings=e;if(!a.isIE){d=c.dom.get(c.id)}b(["getPos","getRect","getParent","add","setStyle","getStyle","setStyles","setAttrib","setAttribs","getAttrib","addClass","removeClass","hasClass","getOuterHTML","setOuterHTML","remove","show","hide","isHidden","setHTML","get"],function(h){c[h]=function(){var j=[g],k;for(k=0;k_';j.deleteContents();j.insertNode(f.getRng().createContextualFragment(i));l=f.dom.get("__caret");j=k.createRange();j.setStartBefore(l);j.setEndAfter(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(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(1);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.firstChild}return h}else{h=g.startContainer;if(h.nodeName=="BODY"){return h.firstChild}return f.dom.getParent(h,"*")}},getEnd:function(){var f=this,g=f.getRng(),h;if(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(0);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.lastChild}return h}else{h=g.endContainer;if(h.nodeName=="BODY"){return h.lastChild}return f.dom.getParent(h,"*")}},getBookmark:function(x){var j=this,m=j.getRng(),f,n,l,u=j.dom.getViewPort(j.win),v,p,z,o,w=-16777215,k,h=j.dom.getRoot(),g=0,i=0,y;n=u.x;l=u.y;if(x){return{rng:m,scrollX:n,scrollY:l}}if(a){if(m.item){v=m.item(0);d(j.dom.select(v.nodeName),function(s,r){if(v==s){p=r;return false}});return{tag:v.nodeName,index:p,scrollX:n,scrollY:l}}f=j.dom.doc.body.createTextRange();f.moveToElementText(h);f.collapse(true);z=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(true);p=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(false);o=Math.abs(f.move("character",w))-p;return{start:p-z,length:o,scrollX:n,scrollY:l}}v=j.getNode();k=j.getSel();if(!k){return null}if(v&&v.nodeName=="IMG"){return{scrollX:n,scrollY:l}}function q(A,D,t){var s=j.dom.doc.createTreeWalker(A,NodeFilter.SHOW_TEXT,null,false),E,B=0,C={};while((E=s.nextNode())!=null){if(E==D){C.start=B}if(E==t){C.end=B;return C}B+=e(E.nodeValue||"").length}return null}if(k.anchorNode==k.focusNode&&k.anchorOffset==k.focusOffset){v=q(h,k.anchorNode,k.focusNode);if(!v){return{scrollX:n,scrollY:l}}e(k.anchorNode.nodeValue||"").replace(/^\s+/,function(r){g=r.length});return{start:Math.max(v.start+k.anchorOffset-g,0),end:Math.max(v.end+k.focusOffset-g,0),scrollX:n,scrollY:l,beg:k.anchorOffset-g==0}}else{v=q(h,m.startContainer,m.endContainer);if(!v){return{scrollX:n,scrollY:l}}return{start:Math.max(v.start+m.startOffset-g,0),end:Math.max(v.end+m.endOffset-i,0),scrollX:n,scrollY:l,beg:m.startOffset-g==0}}},moveToBookmark:function(n){var o=this,g=o.getRng(),p=o.getSel(),j=o.dom.getRoot(),m,h,k;function i(q,t,D){var B=o.dom.doc.createTreeWalker(q,NodeFilter.SHOW_TEXT,null,false),x,s=0,A={},u,C,z,y;while((x=B.nextNode())!=null){z=y=0;k=x.nodeValue||"";h=e(k).length;s+=h;if(s>=t&&!A.startNode){u=t-(s-h);if(n.beg&&u>=h){continue}A.startNode=x;A.startOffset=u+y}if(s>=D){A.endNode=x;A.endOffset=D-(s-h)+y;return A}}return null}if(!n){return false}o.win.scrollTo(n.scrollX,n.scrollY);if(a){if(g=n.rng){try{g.select()}catch(l){}return true}o.win.focus();if(n.tag){g=j.createControlRange();d(o.dom.select(n.tag),function(r,q){if(q==n.index){g.addElement(r)}})}else{try{if(n.start<0){return true}g=p.createRange();g.moveToElementText(j);g.collapse(true);g.moveStart("character",n.start);g.moveEnd("character",n.length)}catch(f){return true}}try{g.select()}catch(l){}return true}if(!p){return false}if(n.rng){p.removeAllRanges();p.addRange(n.rng)}else{if(b(n.start)&&b(n.end)){try{m=i(j,n.start,n.end);if(m){g=o.dom.doc.createRange();g.setStart(m.startNode,m.startOffset);g.setEnd(m.endNode,m.endOffset);p.removeAllRanges();p.addRange(g)}if(!c.isOpera){o.win.focus()}}catch(l){}}}},select:function(g,l){var p=this,f=p.getRng(),q=p.getSel(),o,m,k,j=p.win.document;function h(u,t){var s,r;if(u){s=j.createTreeWalker(u,NodeFilter.SHOW_TEXT,null,false);while(u=s.nextNode()){r=u;if(c.trim(u.nodeValue).length!=0){if(t){return u}else{r=u}}}}return r}if(a){try{o=j.body;if(/^(IMG|TABLE)$/.test(g.nodeName)){f=o.createControlRange();f.addElement(g)}else{f=o.createTextRange();f.moveToElementText(g)}f.select()}catch(i){}}else{if(l){m=h(g,1)||p.dom.select("br:first",g)[0];k=h(g,0)||p.dom.select("br:last",g)[0];if(m&&k){f=j.createRange();if(m.nodeName=="BR"){f.setStartBefore(m)}else{f.setStart(m,0)}if(k.nodeName=="BR"){f.setEndBefore(k)}else{f.setEnd(k,k.nodeValue.length)}}else{f.selectNode(g)}}else{f.selectNode(g)}p.setRng(f)}return g},isCollapsed:function(){var f=this,h=f.getRng(),g=f.getSel();if(!h||h.item){return false}return !g||h.boundingWidth==0||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=a?g.win.document.body.createTextRange():g.win.document.createRange()}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 f=this,h=f.getRng(),g=f.getSel(),i;if(!a){if(!h){return f.dom.getRoot()}i=h.commonAncestorContainer;if(!h.collapsed){if(c.isWebKit&&g.anchorNode&&g.anchorNode.nodeType==1){return g.anchorNode.childNodes[g.anchorOffset]}if(h.startContainer==h.endContainer){if(h.startOffset-h.endOffset<2){if(h.startContainer.hasChildNodes()){i=h.startContainer.childNodes[h.startOffset]}}}}return f.dom.getParent(i,"*")}return h.item?h.item(0):h.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(""+b+">")}if(this.settings.indentation>0){this.writeRaw("\n")}}},writeFullEndElement:function(){if(this.tags.length>0){this._writeAttributesEnd();this.writeRaw(""+this.tags.pop()+">");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",bool_attrs:/(checked|disabled|readonly|selected|nowrap)/,valid_elements:"*[*]",extended_valid_elements:0,valid_child_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,font_size_style_values: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;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""+o+">"}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,y,w=["ol","ul"],u,t,q,k=/^(OL|UL)$/,z;function m(r,x){var o=x.split(","),p;while((r=r.previousSibling)!=null){for(p=0;p1){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]*>)(.*?)(<\/script>)/g},{pattern:/(]*>)(.*?)(<\/noscript>)/g},{pattern:/('; + }); + + // Wrap noscript elements + h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { + return ''; + }); + } + + h = h.replace(//g, ''); + + // Process all tags with src, href or style + h = h.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi, function(a, n) { + function handle(m, b, c) { + var u = c; + + // Tag already got a mce_ version + if (a.indexOf('mce_' + b) != -1) + return m; + + if (b == 'style') { + // No mce_style for elements with these since they might get resized by the user + if (t._isRes(c)) + return m; + + if (s.hex_colors) { + u = u.replace(/rgb\([^\)]+\)/g, function(v) { + return t.toHex(v); + }); + } + + if (s.url_converter) { + u = u.replace(/url\([\'\"]?([^\)\'\"]+)\)/g, function(x, c) { + return 'url(' + t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)) + ')'; + }); + } + } else if (b != 'coords' && b != 'shape') { + if (s.url_converter) + u = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)); + } + + return ' ' + b + '="' + c + '" mce_' + b + '="' + u + '"'; + }; + + a = a.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi, handle); // W3C + a = a.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi, handle); // W3C + + return a.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi, handle); // IE + }); + } + + 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; + + return this.run(e, function(e) { + var n, tp; + + e = t.get(e); + d = d || e.ownerDocument || t.doc; + + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else { + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + } + }); + }, + + decode : function(s) { + var e, n, v; + + // Look for entities to decode + if (/&[^;]+;/.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.nextSibling); + } + + return v || s; + } + + return s; + }, + + encode : function(s) { + return s ? ('' + s).replace(/[<>&\"]/g, function (c, b) { + switch (c) { + case '&': + return '&'; + + case '"': + return '"'; + + case '<': + return '<'; + + case '>': + return '>'; + } + + return c; + }) : s; + }, + + insertAfter : function(n, r) { + var t = this; + + r = t.get(r); + + return this.run(n, function(n) { + var p, ns; + + p = r.parentNode; + ns = r.nextSibling; + + if (ns) + p.insertBefore(n, ns); + else + p.appendChild(n); + + return n; + }); + }, + + isBlock : function(n) { + if (n.nodeType && n.nodeType !== 1) + return false; + + n = n.nodeName || n; + + return /^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.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(o.childNodes, function(c) { + n.appendChild(c.cloneNode(true)); + }); + } + + // Fix IE psuedo leak for elements since replacing elements if fairly common + // Will break parentNode for some unknown reason + if (t.fixPsuedoLeaks && o.nodeType === 1) { + o.parentNode.insertBefore(n, o); + t.remove(o); + return n; + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + 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; + + // 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(/([a-z0-9\:\-_]+)=/gi, function(a, b) { + o.push({specified : 1, nodeName : b}); + }); + + 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); + }, + + 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 sence + // but we don't want that in our code since it serves no purpose + // For example if this is chopped: + // text 1CHOPtext 2 + // would produce: + // text 1CHOPtext 2 + // this function will then trim of empty edges and produce: + // text 1CHOPtext 2 + function trimEdge(n, na) { + n = n[na]; + + if (n && n[na] && n[na].nodeType == 1 && isEmpty(n[na])) + t.remove(n[na]); + }; + + function isEmpty(n) { + n = t.getOuterHTML(n); + n = n.replace(/<(img|hr|table)/gi, '-'); // Keep these convert them to - chars + n = n.replace(/<[^>]+>/g, ''); // Remove all tags + + return n.replace(/[ \t\r\n]+| | /g, '') == ''; + }; + + if (pe && e) { + // Get before chunk + r.setStartBefore(pe); + r.setEndBefore(e); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStartAfter(e); + r.setEndAfter(pe); + aft = r.extractContents(); + + // Insert chunks and remove parent + pa = pe.parentNode; + + // Remove right side edge of the before contents + trimEdge(bef, 'lastChild'); + + if (!isEmpty(bef)) + pa.insertBefore(bef, pe); + + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Remove left site edge of the after contents + trimEdge(aft, 'firstChild'); + + if (!isEmpty(aft)) + pa.insertBefore(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); + }, + + + _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; + } + */ + + }); + + // Setup page DOM + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); +(function(ns) { + // Traverse constants + var EXTRACT = 0, CLONE = 1, DELETE = 2, extend = tinymce.extend; + + function indexOf(child, parent) { + var i, node; + + if (child.parentNode != parent) + return -1; + + for (node = parent.firstChild, i = 0; node != child; node = node.nextSibling) + i++; + + return i; + }; + + function nodeIndex(n) { + var i = 0; + + while (n.previousSibling) { + i++; + n = n.previousSibling; + } + + return i; + }; + + function getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child != null && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child != null) + return child; + + return container; + }; + + // Range constructor + function Range(dom) { + var d = dom.doc; + + extend(this, { + dom : dom, + + // Inital states + startContainer : d, + startOffset : 0, + endContainer : d, + endOffset : 0, + collapsed : true, + commonAncestorContainer : d, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3 + }); + }; + + // Add range methods + extend(Range.prototype, { + setStart : function(n, o) { + this._setEndPoint(true, n, o); + }, + + setEnd : function(n, o) { + this._setEndPoint(false, n, o); + }, + + setStartBefore : function(n) { + this.setStart(n.parentNode, nodeIndex(n)); + }, + + setStartAfter : function(n) { + this.setStart(n.parentNode, nodeIndex(n) + 1); + }, + + setEndBefore : function(n) { + this.setEnd(n.parentNode, nodeIndex(n)); + }, + + setEndAfter : function(n) { + this.setEnd(n.parentNode, nodeIndex(n) + 1); + }, + + collapse : function(ts) { + var t = this; + + if (ts) { + t.endContainer = t.startContainer; + t.endOffset = t.startOffset; + } else { + t.startContainer = t.endContainer; + t.startOffset = t.endOffset; + } + + t.collapsed = true; + }, + + selectNode : function(n) { + this.setStartBefore(n); + this.setEndAfter(n); + }, + + selectNodeContents : function(n) { + this.setStart(n, 0); + this.setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }, + + compareBoundaryPoints : function(h, r) { + var t = this, sc = t.startContainer, so = t.startOffset, ec = t.endContainer, eo = t.endOffset; + + // Check START_TO_START + if (h === 0) + return t._compareBoundaryPoints(sc, so, sc, so); + + // Check START_TO_END + if (h === 1) + return t._compareBoundaryPoints(sc, so, ec, eo); + + // Check END_TO_END + if (h === 2) + return t._compareBoundaryPoints(ec, eo, ec, eo); + + // Check END_TO_START + if (h === 3) + return t._compareBoundaryPoints(ec, eo, sc, so); + }, + + deleteContents : function() { + this._traverse(DELETE); + }, + + extractContents : function() { + return this._traverse(EXTRACT); + }, + + cloneContents : function() { + return this._traverse(CLONE); + }, + + insertNode : function(n) { + var t = this, nn, o; + + // Node is TEXT_NODE or CDATA + if (n.nodeType === 3 || n.nodeType === 4) { + nn = t.startContainer.splitText(t.startOffset); + t.startContainer.parentNode.insertBefore(n, nn); + } else { + // Insert element node + if (t.startContainer.childNodes.length > 0) + o = t.startContainer.childNodes[t.startOffset]; + + t.startContainer.insertBefore(n, o); + } + }, + + surroundContents : function(n) { + var t = this, f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }, + + cloneRange : function() { + var t = this; + + return extend(new Range(t.dom), { + startContainer : t.startContainer, + startOffset : t.startOffset, + endContainer : t.endContainer, + endOffset : t.endOffset, + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }, + +/* + detach : function() { + // Not implemented + }, +*/ + // Internal methods + + _isCollapsed : function() { + return (this.startContainer == this.endContainer && this.startOffset == this.endOffset); + }, + + _compareBoundaryPoints : function (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 + } else if (offsetA < offsetB) { + return -1; // before + } else { + 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 + } else { + 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 + } else { + 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 = this.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; + } + }, + + _setEndPoint : function(st, n, o) { + var t = this, ec, sc; + + if (st) { + t.startContainer = n; + t.startOffset = o; + } else { + t.endContainer = n; + t.endOffset = 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.endContainer; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t.startContainer; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc != ec) { + t.collapse(st); + } else { + // 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 (t._compareBoundaryPoints(t.startContainer, t.startOffset, t.endContainer, t.endOffset) > 0) + t.collapse(st); + } + + t.collapsed = t._isCollapsed(); + t.commonAncestorContainer = t.dom.findCommonAncestor(t.startContainer, t.endContainer); + }, + + // This code is heavily "inspired" by the Apache Xerces implementation. I hope they don't mind. :) + + _traverse : function(how) { + var t = this, c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t.startContainer == t.endContainer) + return t._traverseSameContainer(how); + + for (c = t.endContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.startContainer) + return t._traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t.startContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.endContainer) + return t._traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t.startContainer; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t.endContainer; + 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 t._traverseCommonAncestors(startNode, endNode, how); + }, + + _traverseSameContainer : function(how) { + var t = this, frag, s, sub, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t.startOffset == t.endOffset) + return frag; + + // Text node needs special case handling + if (t.startContainer.nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t.startContainer.nodeValue; + sub = s.substring(t.startOffset, t.endOffset); + + // set the original text node to its new value + if (how != CLONE) { + t.startContainer.deleteData(t.startOffset, t.endOffset - t.startOffset); + + // Nothing is partially selected, so collapse to start point + t.collapse(true); + } + + if (how == DELETE) + return null; + + frag.appendChild(t.dom.doc.createTextNode(sub)); + return frag; + } + + // Copy nodes between the start/end offsets. + n = getSelectedNode(t.startContainer, t.startOffset); + cnt = t.endOffset - t.startOffset; + + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._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; + }, + + _traverseCommonStartContainer : function(endAncestor, how) { + var t = this, frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = indexOf(endAncestor, t.startContainer); + cnt = endIdx - t.startOffset; + + 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 = t._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; + }, + + _traverseCommonEndContainer : function(startAncestor, how) { + var t = this, frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = indexOf(startAncestor, t.endContainer); + ++startIdx; // Because we already traversed it.... + + cnt = t.endOffset - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseCommonAncestors : function(startAncestor, endAncestor, how) { + var t = this, n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = indexOf(startAncestor, commonParent); + endOffset = indexOf(endAncestor, commonParent); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = t._traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseRightBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.endContainer, t.endOffset - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent; + var isFullySelected = next != t.endContainer; + + if (next == root) + return t._traverseNode(next, isFullySelected, false, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, false, how); + + while (parent != null) { + while (next != null) { + prevSibling = next.previousSibling; + clonedChild = t._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 = t._traverseNode(parent, false, false, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseLeftBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.startContainer, t.startOffset); + var isFullySelected = next != t.startContainer, parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return t._traverseNode(next, isFullySelected, true, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, true, how); + + while (parent != null) { + while (next != null) { + nextSibling = next.nextSibling; + clonedChild = t._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 = t._traverseNode(parent, false, true, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseNode : function(n, isFullySelected, isLeft, how) { + var t = this, txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return t._traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t.startOffset; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t.endOffset; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return null; + + newNode = n.cloneNode(false); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return null; + + return n.cloneNode(false); + }, + + _traverseFullySelected : function(n, how) { + var t = this; + + if (how != DELETE) + return how == CLONE ? n.cloneNode(true) : n; + + n.parentNode.removeChild(n); + return null; + } + }); + + ns.Range = Range; +})(tinymce.dom); +(function() { + function Selection(selection) { + var t = this, invisibleChar = '\uFEFF', range, lastIERng; + + 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 1; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return 1; + } + + return 0; + }; + + function getRange() { + var dom = selection.dom, ieRange = selection.getRng(), domRange = dom.createRng(), startPos, endPos, element, sc, ec, collapsed; + + function findIndex(element) { + var nl = element.parentNode.childNodes, i; + + for (i = nl.length - 1; i >= 0; i--) { + if (nl[i] == element) + return i; + } + + return -1; + }; + + function findEndPoint(start) { + var rng = ieRange.duplicate(), parent, i, nl, n, offset = 0, index = 0, pos, tmpRng; + + // Insert marker character + rng.collapse(start); + parent = rng.parentElement(); + rng.pasteHTML(invisibleChar); // Needs to be a pasteHTML instead of .text = since IE has a bug with nodeValue + + // Find marker character + nl = parent.childNodes; + for (i = 0; i < nl.length; i++) { + n = nl[i]; + + // Calculate node index excluding text node fragmentation + if (i > 0 && (n.nodeType !== 3 || nl[i - 1].nodeType !== 3)) + index++; + + // If text node then calculate offset + if (n.nodeType === 3) { + // Look for marker + pos = n.nodeValue.indexOf(invisibleChar); + if (pos !== -1) { + offset += pos; + break; + } + + offset += n.nodeValue.length; + } else + offset = 0; + } + + // Remove marker character + rng.moveStart('character', -1); + rng.text = ''; + + return {index : index, offset : offset, parent : parent}; + }; + + // 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, findIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + // Check collapsed state + collapsed = selection.isCollapsed(); + + // Find start and end pos index and offset + startPos = findEndPoint(true); + endPos = findEndPoint(false); + + // Normalize the elements to avoid fragmented dom + startPos.parent.normalize(); + endPos.parent.normalize(); + + // Set start container and offset + sc = startPos.parent.childNodes[Math.min(startPos.index, startPos.parent.childNodes.length - 1)]; + + if (sc.nodeType != 3) + domRange.setStart(startPos.parent, startPos.index); + else + domRange.setStart(startPos.parent.childNodes[startPos.index], startPos.offset); + + // Set end container and offset + ec = endPos.parent.childNodes[Math.min(endPos.index, endPos.parent.childNodes.length - 1)]; + + if (ec.nodeType != 3) { + if (!collapsed) + endPos.index++; + + domRange.setEnd(endPos.parent, endPos.index); + } else + domRange.setEnd(endPos.parent.childNodes[endPos.index], endPos.offset); + + // If not collapsed then make sure offsets are valid + if (!collapsed) { + sc = domRange.startContainer; + if (sc.nodeType == 1) + domRange.setStart(sc, Math.min(domRange.startOffset, sc.childNodes.length)); + + ec = domRange.endContainer; + if (ec.nodeType == 1) + domRange.setEnd(ec, Math.min(domRange.endOffset, ec.childNodes.length)); + } + + // Restore selection to new range + t.addRange(domRange); + + return domRange; + }; + + this.addRange = function(rng) { + var ieRng, body = selection.dom.doc.body, startPos, endPos, sc, so, ec, eo; + + // Setup some shorter versions + sc = rng.startContainer; + so = rng.startOffset; + ec = rng.endContainer; + eo = rng.endOffset; + ieRng = body.createTextRange(); + + // Find element + sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc; + ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec; + + // 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(); + + return; + } + + function getCharPos(container, offset) { + var nodeVal, rng, pos; + + if (container.nodeType != 3) + return -1; + + nodeVal = container.nodeValue; + rng = body.createTextRange(); + + // Insert marker at offset position + container.nodeValue = nodeVal.substring(0, offset) + invisibleChar + nodeVal.substring(offset); + + // Find char pos of marker and remove it + rng.moveToElementText(container.parentNode); + rng.findText(invisibleChar); + pos = Math.abs(rng.moveStart('character', -0xFFFFF)); + container.nodeValue = nodeVal; + + return pos; + }; + + // Collapsed range + if (rng.collapsed) { + pos = getCharPos(sc, so); + + ieRng = body.createTextRange(); + ieRng.move('character', pos); + ieRng.select(); + + return; + } else { + // If same text container + if (sc == ec && sc.nodeType == 3) { + startPos = getCharPos(sc, so); + + ieRng.move('character', startPos); + ieRng.moveEnd('character', eo - so); + ieRng.select(); + + return; + } + + // Get caret positions + startPos = getCharPos(sc, so); + endPos = getCharPos(ec, eo); + ieRng = body.createTextRange(); + + // Move start of range to start character position or start element + if (startPos == -1) { + ieRng.moveToElementText(sc); + startPos = 0; + } else + ieRng.move('character', startPos); + + // Move end of range to end character position or end element + tmpRng = body.createTextRange(); + + if (endPos == -1) + tmpRng.moveToElementText(ec); + else + tmpRng.move('character', endPos); + + ieRng.setEndPoint('EndToEnd', tmpRng); + ieRng.select(); + + return; + } + }; + + 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; + }; + }; + + // 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; + } + } + + }); + + // Shorten name and setup global instance + 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) { + var each = tinymce.each; + + tinymce.create('tinymce.dom.Element', { + Element : function(id, s) { + var t = this, dom, el; + + s = s || {}; + t.id = id; + t.dom = dom = s.dom || tinymce.DOM; + t.settings = s; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = t.dom.get(t.id); + + each([ + 'getPos', + 'getRect', + 'getParent', + 'add', + 'setStyle', + 'getStyle', + 'setStyles', + 'setAttrib', + 'setAttribs', + 'getAttrib', + 'addClass', + 'removeClass', + 'hasClass', + 'getOuterHTML', + 'setOuterHTML', + 'remove', + 'show', + 'hide', + 'isHidden', + 'setHTML', + 'get' + ], 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; + }; + }); + }, + + on : function(n, f, s) { + return tinymce.dom.Event.add(this.id, n, f, s); + }, + + getXY : function() { + return { + x : parseInt(this.getStyle('left')), + y : parseInt(this.getStyle('top')) + }; + }, + + getSize : function() { + var n = this.dom.get(this.id); + + return { + w : parseInt(this.getStyle('width') || n.clientWidth), + h : parseInt(this.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + this.setStyles({left : x, top : y}); + }, + + moveBy : function(x, y) { + var p = this.getXY(); + + this.moveTo(p.x + x, p.y + y); + }, + + resizeTo : function(w, h) { + this.setStyles({width : w, height : h}); + }, + + resizeBy : function(w, h) { + var s = this.getSize(); + + this.resizeTo(s.w + w, s.h + h); + }, + + update : function(k) { + var t = this, b, dom = t.dom; + + if (tinymce.isIE6 && t.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(t.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.setStyle(b, 'left', t.getStyle('left', 1)); + dom.setStyle(b, 'top', t.getStyle('top', 1)); + dom.setStyle(b, 'width', t.getStyle('width', 1)); + dom.setStyle(b, 'height', t.getStyle('height', 1)); + dom.setStyle(b, 'display', t.getStyle('display', 1)); + dom.setStyle(b, '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 + 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.setEndAfter(c); + t.setRng(r); + + // Delete the marker, and hopefully the caret gets placed in the right location + // Removed this since it seems to remove in FF and simply deleting it + // doesn't seem to affect the caret position in any browser + //d.execCommand('Delete', false, null); + + // 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 (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(1); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.firstChild; + + return e; + } else { + e = r.startContainer; + + if (e.nodeName == 'BODY') + return e.firstChild; + + return t.dom.getParent(e, '*'); + } + }, + + getEnd : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild; + + return e; + } else { + e = r.endContainer; + + if (e.nodeName == 'BODY') + return e.lastChild; + + return t.dom.getParent(e, '*'); + } + }, + + getBookmark : function(si) { + var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv; + sx = vp.x; + sy = vp.y; + + // Simple bookmark fast but not as persistent + if (si) + return {rng : r, scrollX : sx, scrollY : sy}; + + // Handle IE + if (isIE) { + // Control selection + if (r.item) { + e = r.item(0); + + each(t.dom.select(e.nodeName), function(n, i) { + if (e == n) { + sp = i; + return false; + } + }); + + return { + tag : e.nodeName, + index : sp, + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + tr = t.dom.doc.body.createTextRange(); + tr.moveToElementText(ro); + tr.collapse(true); + bp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(true); + sp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(false); + le = Math.abs(tr.move('character', c)) - sp; + + return { + start : sp - bp, + length : le, + scrollX : sx, + scrollY : sy + }; + } + + // Handle W3C + e = t.getNode(); + s = t.getSel(); + + if (!s) + return null; + + // Image selection + if (e && e.nodeName == 'IMG') { + return { + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + + function getPos(r, sn, en) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; + + while ((n = w.nextNode()) != null) { + if (n == sn) + d.start = p; + + if (n == en) { + d.end = p; + return d; + } + + p += trimNl(n.nodeValue || '').length; + } + + return null; + }; + + // Caret or selection + if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) { + e = getPos(ro, s.anchorNode, s.focusNode); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + // Count whitespace before + trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + + return { + start : Math.max(e.start + s.anchorOffset - wb, 0), + end : Math.max(e.end + s.focusOffset - wb, 0), + scrollX : sx, + scrollY : sy, + beg : s.anchorOffset - wb == 0 + }; + } else { + e = getPos(ro, r.startContainer, r.endContainer); + + // Count whitespace before start and end container + //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;}); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + return { + start : Math.max(e.start + r.startOffset - wb, 0), + end : Math.max(e.end + r.endOffset - wa, 0), + scrollX : sx, + scrollY : sy, + beg : r.startOffset - wb == 0 + }; + } + }, + + moveToBookmark : function(b) { + var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv; + + function getPos(r, sp, ep) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb; + + while ((n = w.nextNode()) != null) { + wa = wb = 0; + + nv = n.nodeValue || ''; + //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;}); + //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;}); + + nvl = trimNl(nv).length; + p += nvl; + + if (p >= sp && !d.startNode) { + o = sp - (p - nvl); + + // Fix for odd quirk in FF + if (b.beg && o >= nvl) + continue; + + d.startNode = n; + d.startOffset = o + wb; + } + + if (p >= ep) { + d.endNode = n; + d.endOffset = ep - (p - nvl) + wb; + return d; + } + } + + return null; + }; + + if (!b) + return false; + + t.win.scrollTo(b.scrollX, b.scrollY); + + // Handle explorer + if (isIE) { + // Handle simple + if (r = b.rng) { + try { + r.select(); + } catch (ex) { + // Ignore + } + + return true; + } + + t.win.focus(); + + // Handle control bookmark + if (b.tag) { + r = ro.createControlRange(); + + each(t.dom.select(b.tag), function(n, i) { + if (i == b.index) + r.addElement(n); + }); + } else { + // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs + try { + // Incorrect bookmark + if (b.start < 0) + return true; + + r = s.createRange(); + r.moveToElementText(ro); + r.collapse(true); + r.moveStart('character', b.start); + r.moveEnd('character', b.length); + } catch (ex2) { + return true; + } + } + + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + + return true; + } + + // Handle W3C + if (!s) + return false; + + // Handle simple + if (b.rng) { + s.removeAllRanges(); + s.addRange(b.rng); + } else { + if (is(b.start) && is(b.end)) { + try { + sd = getPos(ro, b.start, b.end); + + if (sd) { + r = t.dom.doc.createRange(); + r.setStart(sd.startNode, sd.startOffset); + r.setEnd(sd.endNode, sd.endOffset); + s.removeAllRanges(); + s.addRange(r); + } + + if (!tinymce.isOpera) + t.win.focus(); + } catch (ex) { + // Ignore + } + } + } + }, + + select : function(n, c) { + var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document; + + function find(n, start) { + var walker, o; + + if (n) { + walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + // Find first/last non empty text node + while (n = walker.nextNode()) { + o = n; + + if (tinymce.trim(n.nodeValue).length != 0) { + if (start) + return n; + else + o = n; + } + } + } + + return o; + }; + + if (isIE) { + try { + b = d.body; + + if (/^(IMG|TABLE)$/.test(n.nodeName)) { + r = b.createControlRange(); + r.addElement(n); + } else { + r = b.createTextRange(); + r.moveToElementText(n); + } + + r.select(); + } catch (ex) { + // Throws illigal agrument in IE some times + } + } else { + if (c) { + fn = find(n, 1) || t.dom.select('br:first', n)[0]; + ln = find(n, 0) || t.dom.select('br:last', n)[0]; + + if (fn && ln) { + r = d.createRange(); + + if (fn.nodeName == 'BR') + r.setStartBefore(fn); + else + r.setStart(fn, 0); + + if (ln.nodeName == 'BR') + r.setEndBefore(ln); + else + r.setEnd(ln, ln.nodeValue.length); + } else + r.selectNode(n); + } else + r.selectNode(n); + + t.setRng(r); + } + + return n; + }, + + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + return !s || r.boundingWidth == 0 || 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 = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange(); + + 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, r = t.getRng(), s = t.getSel(), e; + + if (!isIE) { + // Range maybe lost after the editor is made visible again + if (!r) + return t.dom.getRoot(); + + e = r.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!r.collapsed) { + // If the anchor node is a element instead of a text node then return this element + if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1) + return s.anchorNode.childNodes[s.anchorOffset]; + + if (r.startContainer == r.endContainer) { + if (r.startOffset - r.endOffset < 2) { + if (r.startContainer.hasChildNodes()) + e = r.startContainer.childNodes[r.startOffset]; + } + } + } + + return t.dom.getParent(e, '*'); + } + + return r.item ? r.item(0) : r.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('' + n + '>'); + + if (this.settings.indentation > 0) + this.writeRaw('\n'); + } + }, + + writeFullEndElement : function() { + if (this.tags.length > 0) { + this._writeAttributesEnd(); + this.writeRaw('' + this.tags.pop() + '>'); + + 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', + bool_attrs : /(checked|disabled|readonly|selected|nowrap)/, + valid_elements : '*[*]', + extended_valid_elements : 0, + valid_child_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, + font_size_style_values : 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; + + 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 '' + c + '>'; + + 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 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 ]*>)(.*?)(<\/script>)/g}, + {pattern : /(]*>)(.*?)(<\/noscript>)/g}, + {pattern : /('; + }); + + // Wrap noscript elements + h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { + return ''; + }); + } + + h = h.replace(//g, ''); + + // Process all tags with src, href or style + h = h.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi, function(a, n) { + function handle(m, b, c) { + var u = c; + + // Tag already got a mce_ version + if (a.indexOf('mce_' + b) != -1) + return m; + + if (b == 'style') { + // No mce_style for elements with these since they might get resized by the user + if (t._isRes(c)) + return m; + + if (s.hex_colors) { + u = u.replace(/rgb\([^\)]+\)/g, function(v) { + return t.toHex(v); + }); + } + + if (s.url_converter) { + u = u.replace(/url\([\'\"]?([^\)\'\"]+)\)/g, function(x, c) { + return 'url(' + t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)) + ')'; + }); + } + } else if (b != 'coords' && b != 'shape') { + if (s.url_converter) + u = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)); + } + + return ' ' + b + '="' + c + '" mce_' + b + '="' + u + '"'; + }; + + a = a.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi, handle); // W3C + a = a.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi, handle); // W3C + + return a.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi, handle); // IE + }); + } + + 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; + + return this.run(e, function(e) { + var n, tp; + + e = t.get(e); + d = d || e.ownerDocument || t.doc; + + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else { + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + } + }); + }, + + decode : function(s) { + var e, n, v; + + // Look for entities to decode + if (/&[^;]+;/.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.nextSibling); + } + + return v || s; + } + + return s; + }, + + encode : function(s) { + return s ? ('' + s).replace(/[<>&\"]/g, function (c, b) { + switch (c) { + case '&': + return '&'; + + case '"': + return '"'; + + case '<': + return '<'; + + case '>': + return '>'; + } + + return c; + }) : s; + }, + + insertAfter : function(n, r) { + var t = this; + + r = t.get(r); + + return this.run(n, function(n) { + var p, ns; + + p = r.parentNode; + ns = r.nextSibling; + + if (ns) + p.insertBefore(n, ns); + else + p.appendChild(n); + + return n; + }); + }, + + isBlock : function(n) { + if (n.nodeType && n.nodeType !== 1) + return false; + + n = n.nodeName || n; + + return /^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.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(o.childNodes, function(c) { + n.appendChild(c.cloneNode(true)); + }); + } + + // Fix IE psuedo leak for elements since replacing elements if fairly common + // Will break parentNode for some unknown reason + if (t.fixPsuedoLeaks && o.nodeType === 1) { + o.parentNode.insertBefore(n, o); + t.remove(o); + return n; + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + 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; + + // 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(/([a-z0-9\:\-_]+)=/gi, function(a, b) { + o.push({specified : 1, nodeName : b}); + }); + + 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); + }, + + 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 sence + // but we don't want that in our code since it serves no purpose + // For example if this is chopped: + // text 1CHOPtext 2 + // would produce: + // text 1CHOPtext 2 + // this function will then trim of empty edges and produce: + // text 1CHOPtext 2 + function trimEdge(n, na) { + n = n[na]; + + if (n && n[na] && n[na].nodeType == 1 && isEmpty(n[na])) + t.remove(n[na]); + }; + + function isEmpty(n) { + n = t.getOuterHTML(n); + n = n.replace(/<(img|hr|table)/gi, '-'); // Keep these convert them to - chars + n = n.replace(/<[^>]+>/g, ''); // Remove all tags + + return n.replace(/[ \t\r\n]+| | /g, '') == ''; + }; + + if (pe && e) { + // Get before chunk + r.setStartBefore(pe); + r.setEndBefore(e); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStartAfter(e); + r.setEndAfter(pe); + aft = r.extractContents(); + + // Insert chunks and remove parent + pa = pe.parentNode; + + // Remove right side edge of the before contents + trimEdge(bef, 'lastChild'); + + if (!isEmpty(bef)) + pa.insertBefore(bef, pe); + + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Remove left site edge of the after contents + trimEdge(aft, 'firstChild'); + + if (!isEmpty(aft)) + pa.insertBefore(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); + }, + + + _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; + } + */ + + }); + + // Setup page DOM + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); +(function(ns) { + // Traverse constants + var EXTRACT = 0, CLONE = 1, DELETE = 2, extend = tinymce.extend; + + function indexOf(child, parent) { + var i, node; + + if (child.parentNode != parent) + return -1; + + for (node = parent.firstChild, i = 0; node != child; node = node.nextSibling) + i++; + + return i; + }; + + function nodeIndex(n) { + var i = 0; + + while (n.previousSibling) { + i++; + n = n.previousSibling; + } + + return i; + }; + + function getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child != null && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child != null) + return child; + + return container; + }; + + // Range constructor + function Range(dom) { + var d = dom.doc; + + extend(this, { + dom : dom, + + // Inital states + startContainer : d, + startOffset : 0, + endContainer : d, + endOffset : 0, + collapsed : true, + commonAncestorContainer : d, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3 + }); + }; + + // Add range methods + extend(Range.prototype, { + setStart : function(n, o) { + this._setEndPoint(true, n, o); + }, + + setEnd : function(n, o) { + this._setEndPoint(false, n, o); + }, + + setStartBefore : function(n) { + this.setStart(n.parentNode, nodeIndex(n)); + }, + + setStartAfter : function(n) { + this.setStart(n.parentNode, nodeIndex(n) + 1); + }, + + setEndBefore : function(n) { + this.setEnd(n.parentNode, nodeIndex(n)); + }, + + setEndAfter : function(n) { + this.setEnd(n.parentNode, nodeIndex(n) + 1); + }, + + collapse : function(ts) { + var t = this; + + if (ts) { + t.endContainer = t.startContainer; + t.endOffset = t.startOffset; + } else { + t.startContainer = t.endContainer; + t.startOffset = t.endOffset; + } + + t.collapsed = true; + }, + + selectNode : function(n) { + this.setStartBefore(n); + this.setEndAfter(n); + }, + + selectNodeContents : function(n) { + this.setStart(n, 0); + this.setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }, + + compareBoundaryPoints : function(h, r) { + var t = this, sc = t.startContainer, so = t.startOffset, ec = t.endContainer, eo = t.endOffset; + + // Check START_TO_START + if (h === 0) + return t._compareBoundaryPoints(sc, so, sc, so); + + // Check START_TO_END + if (h === 1) + return t._compareBoundaryPoints(sc, so, ec, eo); + + // Check END_TO_END + if (h === 2) + return t._compareBoundaryPoints(ec, eo, ec, eo); + + // Check END_TO_START + if (h === 3) + return t._compareBoundaryPoints(ec, eo, sc, so); + }, + + deleteContents : function() { + this._traverse(DELETE); + }, + + extractContents : function() { + return this._traverse(EXTRACT); + }, + + cloneContents : function() { + return this._traverse(CLONE); + }, + + insertNode : function(n) { + var t = this, nn, o; + + // Node is TEXT_NODE or CDATA + if (n.nodeType === 3 || n.nodeType === 4) { + nn = t.startContainer.splitText(t.startOffset); + t.startContainer.parentNode.insertBefore(n, nn); + } else { + // Insert element node + if (t.startContainer.childNodes.length > 0) + o = t.startContainer.childNodes[t.startOffset]; + + t.startContainer.insertBefore(n, o); + } + }, + + surroundContents : function(n) { + var t = this, f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }, + + cloneRange : function() { + var t = this; + + return extend(new Range(t.dom), { + startContainer : t.startContainer, + startOffset : t.startOffset, + endContainer : t.endContainer, + endOffset : t.endOffset, + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }, + +/* + detach : function() { + // Not implemented + }, +*/ + // Internal methods + + _isCollapsed : function() { + return (this.startContainer == this.endContainer && this.startOffset == this.endOffset); + }, + + _compareBoundaryPoints : function (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 + } else if (offsetA < offsetB) { + return -1; // before + } else { + 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 + } else { + 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 + } else { + 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 = this.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; + } + }, + + _setEndPoint : function(st, n, o) { + var t = this, ec, sc; + + if (st) { + t.startContainer = n; + t.startOffset = o; + } else { + t.endContainer = n; + t.endOffset = 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.endContainer; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t.startContainer; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc != ec) { + t.collapse(st); + } else { + // 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 (t._compareBoundaryPoints(t.startContainer, t.startOffset, t.endContainer, t.endOffset) > 0) + t.collapse(st); + } + + t.collapsed = t._isCollapsed(); + t.commonAncestorContainer = t.dom.findCommonAncestor(t.startContainer, t.endContainer); + }, + + // This code is heavily "inspired" by the Apache Xerces implementation. I hope they don't mind. :) + + _traverse : function(how) { + var t = this, c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t.startContainer == t.endContainer) + return t._traverseSameContainer(how); + + for (c = t.endContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.startContainer) + return t._traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t.startContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.endContainer) + return t._traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t.startContainer; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t.endContainer; + 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 t._traverseCommonAncestors(startNode, endNode, how); + }, + + _traverseSameContainer : function(how) { + var t = this, frag, s, sub, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t.startOffset == t.endOffset) + return frag; + + // Text node needs special case handling + if (t.startContainer.nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t.startContainer.nodeValue; + sub = s.substring(t.startOffset, t.endOffset); + + // set the original text node to its new value + if (how != CLONE) { + t.startContainer.deleteData(t.startOffset, t.endOffset - t.startOffset); + + // Nothing is partially selected, so collapse to start point + t.collapse(true); + } + + if (how == DELETE) + return null; + + frag.appendChild(t.dom.doc.createTextNode(sub)); + return frag; + } + + // Copy nodes between the start/end offsets. + n = getSelectedNode(t.startContainer, t.startOffset); + cnt = t.endOffset - t.startOffset; + + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._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; + }, + + _traverseCommonStartContainer : function(endAncestor, how) { + var t = this, frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = indexOf(endAncestor, t.startContainer); + cnt = endIdx - t.startOffset; + + 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 = t._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; + }, + + _traverseCommonEndContainer : function(startAncestor, how) { + var t = this, frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = indexOf(startAncestor, t.endContainer); + ++startIdx; // Because we already traversed it.... + + cnt = t.endOffset - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseCommonAncestors : function(startAncestor, endAncestor, how) { + var t = this, n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = indexOf(startAncestor, commonParent); + endOffset = indexOf(endAncestor, commonParent); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = t._traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseRightBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.endContainer, t.endOffset - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent; + var isFullySelected = next != t.endContainer; + + if (next == root) + return t._traverseNode(next, isFullySelected, false, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, false, how); + + while (parent != null) { + while (next != null) { + prevSibling = next.previousSibling; + clonedChild = t._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 = t._traverseNode(parent, false, false, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseLeftBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.startContainer, t.startOffset); + var isFullySelected = next != t.startContainer, parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return t._traverseNode(next, isFullySelected, true, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, true, how); + + while (parent != null) { + while (next != null) { + nextSibling = next.nextSibling; + clonedChild = t._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 = t._traverseNode(parent, false, true, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseNode : function(n, isFullySelected, isLeft, how) { + var t = this, txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return t._traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t.startOffset; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t.endOffset; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return null; + + newNode = n.cloneNode(false); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return null; + + return n.cloneNode(false); + }, + + _traverseFullySelected : function(n, how) { + var t = this; + + if (how != DELETE) + return how == CLONE ? n.cloneNode(true) : n; + + n.parentNode.removeChild(n); + return null; + } + }); + + ns.Range = Range; +})(tinymce.dom); +(function() { + function Selection(selection) { + var t = this, invisibleChar = '\uFEFF', range, lastIERng; + + 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 1; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return 1; + } + + return 0; + }; + + function getRange() { + var dom = selection.dom, ieRange = selection.getRng(), domRange = dom.createRng(), startPos, endPos, element, sc, ec, collapsed; + + function findIndex(element) { + var nl = element.parentNode.childNodes, i; + + for (i = nl.length - 1; i >= 0; i--) { + if (nl[i] == element) + return i; + } + + return -1; + }; + + function findEndPoint(start) { + var rng = ieRange.duplicate(), parent, i, nl, n, offset = 0, index = 0, pos, tmpRng; + + // Insert marker character + rng.collapse(start); + parent = rng.parentElement(); + rng.pasteHTML(invisibleChar); // Needs to be a pasteHTML instead of .text = since IE has a bug with nodeValue + + // Find marker character + nl = parent.childNodes; + for (i = 0; i < nl.length; i++) { + n = nl[i]; + + // Calculate node index excluding text node fragmentation + if (i > 0 && (n.nodeType !== 3 || nl[i - 1].nodeType !== 3)) + index++; + + // If text node then calculate offset + if (n.nodeType === 3) { + // Look for marker + pos = n.nodeValue.indexOf(invisibleChar); + if (pos !== -1) { + offset += pos; + break; + } + + offset += n.nodeValue.length; + } else + offset = 0; + } + + // Remove marker character + rng.moveStart('character', -1); + rng.text = ''; + + return {index : index, offset : offset, parent : parent}; + }; + + // 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, findIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + // Check collapsed state + collapsed = selection.isCollapsed(); + + // Find start and end pos index and offset + startPos = findEndPoint(true); + endPos = findEndPoint(false); + + // Normalize the elements to avoid fragmented dom + startPos.parent.normalize(); + endPos.parent.normalize(); + + // Set start container and offset + sc = startPos.parent.childNodes[Math.min(startPos.index, startPos.parent.childNodes.length - 1)]; + + if (sc.nodeType != 3) + domRange.setStart(startPos.parent, startPos.index); + else + domRange.setStart(startPos.parent.childNodes[startPos.index], startPos.offset); + + // Set end container and offset + ec = endPos.parent.childNodes[Math.min(endPos.index, endPos.parent.childNodes.length - 1)]; + + if (ec.nodeType != 3) { + if (!collapsed) + endPos.index++; + + domRange.setEnd(endPos.parent, endPos.index); + } else + domRange.setEnd(endPos.parent.childNodes[endPos.index], endPos.offset); + + // If not collapsed then make sure offsets are valid + if (!collapsed) { + sc = domRange.startContainer; + if (sc.nodeType == 1) + domRange.setStart(sc, Math.min(domRange.startOffset, sc.childNodes.length)); + + ec = domRange.endContainer; + if (ec.nodeType == 1) + domRange.setEnd(ec, Math.min(domRange.endOffset, ec.childNodes.length)); + } + + // Restore selection to new range + t.addRange(domRange); + + return domRange; + }; + + this.addRange = function(rng) { + var ieRng, body = selection.dom.doc.body, startPos, endPos, sc, so, ec, eo; + + // Setup some shorter versions + sc = rng.startContainer; + so = rng.startOffset; + ec = rng.endContainer; + eo = rng.endOffset; + ieRng = body.createTextRange(); + + // Find element + sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc; + ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec; + + // 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(); + + return; + } + + function getCharPos(container, offset) { + var nodeVal, rng, pos; + + if (container.nodeType != 3) + return -1; + + nodeVal = container.nodeValue; + rng = body.createTextRange(); + + // Insert marker at offset position + container.nodeValue = nodeVal.substring(0, offset) + invisibleChar + nodeVal.substring(offset); + + // Find char pos of marker and remove it + rng.moveToElementText(container.parentNode); + rng.findText(invisibleChar); + pos = Math.abs(rng.moveStart('character', -0xFFFFF)); + container.nodeValue = nodeVal; + + return pos; + }; + + // Collapsed range + if (rng.collapsed) { + pos = getCharPos(sc, so); + + ieRng = body.createTextRange(); + ieRng.move('character', pos); + ieRng.select(); + + return; + } else { + // If same text container + if (sc == ec && sc.nodeType == 3) { + startPos = getCharPos(sc, so); + + ieRng.move('character', startPos); + ieRng.moveEnd('character', eo - so); + ieRng.select(); + + return; + } + + // Get caret positions + startPos = getCharPos(sc, so); + endPos = getCharPos(ec, eo); + ieRng = body.createTextRange(); + + // Move start of range to start character position or start element + if (startPos == -1) { + ieRng.moveToElementText(sc); + startPos = 0; + } else + ieRng.move('character', startPos); + + // Move end of range to end character position or end element + tmpRng = body.createTextRange(); + + if (endPos == -1) + tmpRng.moveToElementText(ec); + else + tmpRng.move('character', endPos); + + ieRng.setEndPoint('EndToEnd', tmpRng); + ieRng.select(); + + return; + } + }; + + 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; + }; + }; + + // 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; + } + } + + }); + + // Shorten name and setup global instance + 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) { + var each = tinymce.each; + + tinymce.create('tinymce.dom.Element', { + Element : function(id, s) { + var t = this, dom, el; + + s = s || {}; + t.id = id; + t.dom = dom = s.dom || tinymce.DOM; + t.settings = s; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = t.dom.get(t.id); + + each([ + 'getPos', + 'getRect', + 'getParent', + 'add', + 'setStyle', + 'getStyle', + 'setStyles', + 'setAttrib', + 'setAttribs', + 'getAttrib', + 'addClass', + 'removeClass', + 'hasClass', + 'getOuterHTML', + 'setOuterHTML', + 'remove', + 'show', + 'hide', + 'isHidden', + 'setHTML', + 'get' + ], 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; + }; + }); + }, + + on : function(n, f, s) { + return tinymce.dom.Event.add(this.id, n, f, s); + }, + + getXY : function() { + return { + x : parseInt(this.getStyle('left')), + y : parseInt(this.getStyle('top')) + }; + }, + + getSize : function() { + var n = this.dom.get(this.id); + + return { + w : parseInt(this.getStyle('width') || n.clientWidth), + h : parseInt(this.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + this.setStyles({left : x, top : y}); + }, + + moveBy : function(x, y) { + var p = this.getXY(); + + this.moveTo(p.x + x, p.y + y); + }, + + resizeTo : function(w, h) { + this.setStyles({width : w, height : h}); + }, + + resizeBy : function(w, h) { + var s = this.getSize(); + + this.resizeTo(s.w + w, s.h + h); + }, + + update : function(k) { + var t = this, b, dom = t.dom; + + if (tinymce.isIE6 && t.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(t.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.setStyle(b, 'left', t.getStyle('left', 1)); + dom.setStyle(b, 'top', t.getStyle('top', 1)); + dom.setStyle(b, 'width', t.getStyle('width', 1)); + dom.setStyle(b, 'height', t.getStyle('height', 1)); + dom.setStyle(b, 'display', t.getStyle('display', 1)); + dom.setStyle(b, '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 + 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.setEndAfter(c); + t.setRng(r); + + // Delete the marker, and hopefully the caret gets placed in the right location + // Removed this since it seems to remove in FF and simply deleting it + // doesn't seem to affect the caret position in any browser + //d.execCommand('Delete', false, null); + + // 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 (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(1); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.firstChild; + + return e; + } else { + e = r.startContainer; + + if (e.nodeName == 'BODY') + return e.firstChild; + + return t.dom.getParent(e, '*'); + } + }, + + getEnd : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild; + + return e; + } else { + e = r.endContainer; + + if (e.nodeName == 'BODY') + return e.lastChild; + + return t.dom.getParent(e, '*'); + } + }, + + getBookmark : function(si) { + var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv; + sx = vp.x; + sy = vp.y; + + // Simple bookmark fast but not as persistent + if (si) + return {rng : r, scrollX : sx, scrollY : sy}; + + // Handle IE + if (isIE) { + // Control selection + if (r.item) { + e = r.item(0); + + each(t.dom.select(e.nodeName), function(n, i) { + if (e == n) { + sp = i; + return false; + } + }); + + return { + tag : e.nodeName, + index : sp, + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + tr = t.dom.doc.body.createTextRange(); + tr.moveToElementText(ro); + tr.collapse(true); + bp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(true); + sp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(false); + le = Math.abs(tr.move('character', c)) - sp; + + return { + start : sp - bp, + length : le, + scrollX : sx, + scrollY : sy + }; + } + + // Handle W3C + e = t.getNode(); + s = t.getSel(); + + if (!s) + return null; + + // Image selection + if (e && e.nodeName == 'IMG') { + return { + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + + function getPos(r, sn, en) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; + + while ((n = w.nextNode()) != null) { + if (n == sn) + d.start = p; + + if (n == en) { + d.end = p; + return d; + } + + p += trimNl(n.nodeValue || '').length; + } + + return null; + }; + + // Caret or selection + if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) { + e = getPos(ro, s.anchorNode, s.focusNode); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + // Count whitespace before + trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + + return { + start : Math.max(e.start + s.anchorOffset - wb, 0), + end : Math.max(e.end + s.focusOffset - wb, 0), + scrollX : sx, + scrollY : sy, + beg : s.anchorOffset - wb == 0 + }; + } else { + e = getPos(ro, r.startContainer, r.endContainer); + + // Count whitespace before start and end container + //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;}); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + return { + start : Math.max(e.start + r.startOffset - wb, 0), + end : Math.max(e.end + r.endOffset - wa, 0), + scrollX : sx, + scrollY : sy, + beg : r.startOffset - wb == 0 + }; + } + }, + + moveToBookmark : function(b) { + var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv; + + function getPos(r, sp, ep) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb; + + while ((n = w.nextNode()) != null) { + wa = wb = 0; + + nv = n.nodeValue || ''; + //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;}); + //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;}); + + nvl = trimNl(nv).length; + p += nvl; + + if (p >= sp && !d.startNode) { + o = sp - (p - nvl); + + // Fix for odd quirk in FF + if (b.beg && o >= nvl) + continue; + + d.startNode = n; + d.startOffset = o + wb; + } + + if (p >= ep) { + d.endNode = n; + d.endOffset = ep - (p - nvl) + wb; + return d; + } + } + + return null; + }; + + if (!b) + return false; + + t.win.scrollTo(b.scrollX, b.scrollY); + + // Handle explorer + if (isIE) { + // Handle simple + if (r = b.rng) { + try { + r.select(); + } catch (ex) { + // Ignore + } + + return true; + } + + t.win.focus(); + + // Handle control bookmark + if (b.tag) { + r = ro.createControlRange(); + + each(t.dom.select(b.tag), function(n, i) { + if (i == b.index) + r.addElement(n); + }); + } else { + // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs + try { + // Incorrect bookmark + if (b.start < 0) + return true; + + r = s.createRange(); + r.moveToElementText(ro); + r.collapse(true); + r.moveStart('character', b.start); + r.moveEnd('character', b.length); + } catch (ex2) { + return true; + } + } + + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + + return true; + } + + // Handle W3C + if (!s) + return false; + + // Handle simple + if (b.rng) { + s.removeAllRanges(); + s.addRange(b.rng); + } else { + if (is(b.start) && is(b.end)) { + try { + sd = getPos(ro, b.start, b.end); + + if (sd) { + r = t.dom.doc.createRange(); + r.setStart(sd.startNode, sd.startOffset); + r.setEnd(sd.endNode, sd.endOffset); + s.removeAllRanges(); + s.addRange(r); + } + + if (!tinymce.isOpera) + t.win.focus(); + } catch (ex) { + // Ignore + } + } + } + }, + + select : function(n, c) { + var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document; + + function find(n, start) { + var walker, o; + + if (n) { + walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + // Find first/last non empty text node + while (n = walker.nextNode()) { + o = n; + + if (tinymce.trim(n.nodeValue).length != 0) { + if (start) + return n; + else + o = n; + } + } + } + + return o; + }; + + if (isIE) { + try { + b = d.body; + + if (/^(IMG|TABLE)$/.test(n.nodeName)) { + r = b.createControlRange(); + r.addElement(n); + } else { + r = b.createTextRange(); + r.moveToElementText(n); + } + + r.select(); + } catch (ex) { + // Throws illigal agrument in IE some times + } + } else { + if (c) { + fn = find(n, 1) || t.dom.select('br:first', n)[0]; + ln = find(n, 0) || t.dom.select('br:last', n)[0]; + + if (fn && ln) { + r = d.createRange(); + + if (fn.nodeName == 'BR') + r.setStartBefore(fn); + else + r.setStart(fn, 0); + + if (ln.nodeName == 'BR') + r.setEndBefore(ln); + else + r.setEnd(ln, ln.nodeValue.length); + } else + r.selectNode(n); + } else + r.selectNode(n); + + t.setRng(r); + } + + return n; + }, + + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + return !s || r.boundingWidth == 0 || 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 = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange(); + + 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, r = t.getRng(), s = t.getSel(), e; + + if (!isIE) { + // Range maybe lost after the editor is made visible again + if (!r) + return t.dom.getRoot(); + + e = r.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!r.collapsed) { + // If the anchor node is a element instead of a text node then return this element + if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1) + return s.anchorNode.childNodes[s.anchorOffset]; + + if (r.startContainer == r.endContainer) { + if (r.startOffset - r.endOffset < 2) { + if (r.startContainer.hasChildNodes()) + e = r.startContainer.childNodes[r.startOffset]; + } + } + } + + return t.dom.getParent(e, '*'); + } + + return r.item ? r.item(0) : r.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('' + n + '>'); + + if (this.settings.indentation > 0) + this.writeRaw('\n'); + } + }, + + writeFullEndElement : function() { + if (this.tags.length > 0) { + this._writeAttributesEnd(); + this.writeRaw('' + this.tags.pop() + '>'); + + 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', + bool_attrs : /(checked|disabled|readonly|selected|nowrap)/, + valid_elements : '*[*]', + extended_valid_elements : 0, + valid_child_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, + font_size_style_values : 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; + + 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 '' + c + '>'; + + 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 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 ]*>)(.*?)(<\/script>)/g}, + {pattern : /(]*>)(.*?)(<\/noscript>)/g}, + {pattern : /(
']); + t.addShortcut('ctrl+8', '', ['FormatBlock', false, '
Content
') : val; + + if (val.indexOf('<') == -1) + val = '<' + val + '>'; + + if (tinymce.isGecko) + val = val.replace(/<(div|blockquote|code|dt|dd|dl|samp)>/gi, '$1'); + + ed.getDoc().execCommand('FormatBlock', false, val); + }, + + mceCleanup : function() { + var ed = this.editor, s = ed.selection, b = s.getBookmark(); + ed.setContent(ed.getContent()); + s.moveToBookmark(b); + }, + + mceRemoveNode : function(ui, val) { + var ed = this.editor, s = ed.selection, b, n = val || s.getNode(); + + // Make sure that the body node isn't removed + if (n == ed.getBody()) + return; + + b = s.getBookmark(); + ed.dom.remove(n, 1); + s.moveToBookmark(b); + ed.nodeChanged(); + }, + + mceSelectNodeDepth : function(ui, val) { + var ed = this.editor, s = ed.selection, c = 0; + + ed.dom.getParent(s.getNode(), function(n) { + if (n.nodeType == 1 && c++ == val) { + s.select(n); + ed.nodeChanged(); + return false; + } + }, ed.getBody()); + }, + + mceSelectNode : function(u, v) { + this.editor.selection.select(v); + }, + + mceInsertContent : function(ui, val) { + this.editor.selection.setContent(val); + }, + + mceInsertRawHTML : function(ui, val) { + var ed = this.editor; + + ed.selection.setContent('tiny_mce_marker'); + ed.setContent(ed.getContent().replace(/tiny_mce_marker/g, val)); + }, + + mceRepaint : function() { + var s, b, e = this.editor; + + if (tinymce.isGecko) { + try { + s = e.selection; + b = s.getBookmark(true); + + if (s.getSel()) + s.getSel().selectAllChildren(e.getBody()); + + s.collapse(true); + s.moveToBookmark(b); + } catch (ex) { + // Ignore + } + } + }, + + queryStateUnderline : function() { + var ed = this.editor, n = ed.selection.getNode(); + + if (n && n.nodeName == 'A') + return false; + + return this._queryState('Underline'); + }, + + queryStateOutdent : function() { + var ed = this.editor, n; + + if (ed.settings.inline_styles) { + if ((n = ed.dom.getParent(ed.selection.getStart(), ed.dom.isBlock)) && parseInt(n.style.paddingLeft) > 0) + return true; + + if ((n = ed.dom.getParent(ed.selection.getEnd(), ed.dom.isBlock)) && parseInt(n.style.paddingLeft) > 0) + return true; + } + + return this.queryStateInsertUnorderedList() || this.queryStateInsertOrderedList() || (!ed.settings.inline_styles && !!ed.dom.getParent(ed.selection.getNode(), 'BLOCKQUOTE')); + }, + + queryStateInsertUnorderedList : function() { + return this.editor.dom.getParent(this.editor.selection.getNode(), 'UL'); + }, + + queryStateInsertOrderedList : function() { + return this.editor.dom.getParent(this.editor.selection.getNode(), 'OL'); + }, + + queryStatemceBlockQuote : function() { + return !!this.editor.dom.getParent(this.editor.selection.getStart(), function(n) {return n.nodeName === 'BLOCKQUOTE';}); + }, + + _applyInlineStyle : function(na, at, op) { + var t = this, ed = t.editor, dom = ed.dom, bm, lo = {}, kh, found; + + na = na.toUpperCase(); + + if (op && op.check_classes && at['class']) + op.check_classes.push(at['class']); + + function removeEmpty() { + each(dom.select(na).reverse(), function(n) { + var c = 0; + + // Check if there is any attributes + each(dom.getAttribs(n), function(an) { + if (an.nodeName.substring(0, 1) != '_' && dom.getAttrib(n, an.nodeName) != '') { + //console.log(dom.getOuterHTML(n), dom.getAttrib(n, an.nodeName)); + c++; + } + }); + + // No attributes then remove the element and keep the children + if (c == 0) + dom.remove(n, 1); + }); + }; + + function replaceFonts() { + var bm; + + each(dom.select('span,font'), function(n) { + if (n.style.fontFamily == 'mceinline' || n.face == 'mceinline') { + if (!bm) + bm = ed.selection.getBookmark(); + + at._mce_new = '1'; + dom.replace(dom.create(na, at), n, 1); + } + }); + + // Remove redundant elements + each(dom.select(na + '[_mce_new]'), function(n) { + function removeStyle(n) { + if (n.nodeType == 1) { + each(at.style, function(v, k) { + dom.setStyle(n, k, ''); + }); + + // Remove spans with the same class or marked classes + if (at['class'] && n.className && op) { + each(op.check_classes, function(c) { + if (dom.hasClass(n, c)) + dom.removeClass(n, c); + }); + } + } + }; + + // Remove specified style information from child elements + each(dom.select(na, n), removeStyle); + + // Remove the specified style information on parent if current node is only child (IE) + if (n.parentNode && n.parentNode.nodeType == 1 && n.parentNode.childNodes.length == 1) + removeStyle(n.parentNode); + + // Remove the child elements style info if a parent already has it + dom.getParent(n.parentNode, function(pn) { + if (pn.nodeType == 1) { + if (at.style) { + each(at.style, function(v, k) { + var sv; + + if (!lo[k] && (sv = dom.getStyle(pn, k))) { + if (sv === v) + dom.setStyle(n, k, ''); + + lo[k] = 1; + } + }); + } + + // Remove spans with the same class or marked classes + if (at['class'] && pn.className && op) { + each(op.check_classes, function(c) { + if (dom.hasClass(pn, c)) + dom.removeClass(n, c); + }); + } + } + + return false; + }); + + n.removeAttribute('_mce_new'); + }); + + removeEmpty(); + ed.selection.moveToBookmark(bm); + + return !!bm; + }; + + // Create inline elements + ed.focus(); + ed.getDoc().execCommand('FontName', false, 'mceinline'); + replaceFonts(); + + if (kh = t._applyInlineStyle.keyhandler) { + ed.onKeyUp.remove(kh); + ed.onKeyPress.remove(kh); + ed.onKeyDown.remove(kh); + ed.onSetContent.remove(t._applyInlineStyle.chandler); + } + + if (ed.selection.isCollapsed()) { + // IE will format the current word so this code can't be executed on that browser + if (!isIE) { + each(dom.getParents(ed.selection.getNode(), 'span'), function(n) { + each(at.style, function(v, k) { + var kv; + + if (kv = dom.getStyle(n, k)) { + if (kv == v) { + dom.setStyle(n, k, ''); + found = 2; + return false; + } + + found = 1; + return false; + } + }); + + if (found) + return false; + }); + + if (found == 2) { + bm = ed.selection.getBookmark(); + + removeEmpty(); + + ed.selection.moveToBookmark(bm); + + // Node change needs to be detached since the onselect event + // for the select box will run the onclick handler after onselect call. Todo: Add a nicer fix! + window.setTimeout(function() { + ed.nodeChanged(); + }, 1); + + return; + } + } + + // Start collecting styles + t._pendingStyles = tinymce.extend(t._pendingStyles || {}, at.style); + + t._applyInlineStyle.chandler = ed.onSetContent.add(function() { + delete t._pendingStyles; + }); + + t._applyInlineStyle.keyhandler = kh = function(e) { + // Use pending styles + if (t._pendingStyles) { + at.style = t._pendingStyles; + delete t._pendingStyles; + } + + if (replaceFonts()) { + ed.onKeyDown.remove(t._applyInlineStyle.keyhandler); + ed.onKeyPress.remove(t._applyInlineStyle.keyhandler); + } + + if (e.type == 'keyup') + ed.onKeyUp.remove(t._applyInlineStyle.keyhandler); + }; + + ed.onKeyDown.add(kh); + ed.onKeyPress.add(kh); + ed.onKeyUp.add(kh); + } else + t._pendingStyles = 0; + } + }); +})(tinymce); \ No newline at end of file diff --git a/js/tiny_mce/classes/EditorManager.js b/js/tiny_mce/classes/EditorManager.js new file mode 100644 index 0000000000..69d8970449 --- /dev/null +++ b/js/tiny_mce/classes/EditorManager.js @@ -0,0 +1,453 @@ +/** + * $Id: EditorManager.js 1160 2009-06-18 18:54:44Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(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; + + /**#@+ + * @class This class is used to create multiple editor instances and contain them in a collection. So it's both a factory and a manager for editor instances. + * @static + * @member tinymce.EditorManager + */ + tinymce.create('static tinymce.EditorManager', { + editors : {}, + i18n : {}, + activeEditor : null, + + /**#@+ + * @method + */ + + /** + * Preinitializes the EditorManager class. This method will be called automatically when the page loads and it + * will setup some important paths and URIs and attach some document events. + */ + preInit : function() { + var t = this, lo = window.location; + + // Setup some URLs where the editor API is located and where the document is + tinymce.documentBaseURL = lo.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); + if (!/[\/\\]$/.test(tinymce.documentBaseURL)) + tinymce.documentBaseURL += '/'; + + tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); + tinymce.EditorManager.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 + t.onBeforeUnload = new tinymce.util.Dispatcher(t); + + // Must be on window or IE will leak if the editor is placed in frame or iframe + Event.add(window, 'beforeunload', function(e) { + t.onBeforeUnload.dispatch(t, e); + }); + }, + + /** + * Initializes a set of editors. This method will create a bunch of editors based in the input. + * + * @param {Object} s Settings object to be passed to each editor instance. + */ + init : function(s) { + var t = this, pl, sl = tinymce.ScriptLoader, c, 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", + strict_loading_mode : document.contentType == 'application/xhtml+xml' + }, s); + + t.settings = s; + + // If page not loaded and strict mode isn't enabled then load them + if (!Event.domLoaded && !s.strict_loading_mode) { + // Load language + if (s.language) + sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); + + // Load theme + if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) + ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); + + // Load plugins + if (s.plugins) { + pl = explode(s.plugins); + + // Load compat2x first + if (tinymce.inArray(pl, 'compat2x') != -1) + PluginManager.load('compat2x', 'plugins/compat2x/editor_plugin' + tinymce.suffix + '.js'); + + // Load rest if plugins + each(pl, function(v) { + if (v && v.charAt(0) != '-' && !PluginManager.urls[v]) { + // Skip safari plugin for other browsers + if (!tinymce.isWebKit && v == 'safari') + return; + + PluginManager.load(v, 'plugins/' + v + '/editor_plugin' + tinymce.suffix + '.js'); + } + }); + } + + sl.loadQueue(); + } + + // Legacy call + Event.add(document, 'init', function() { + var l, co; + + execCallback(s, 'onpageload'); + + // Verify that it's a valid browser + if (s.browsers) { + l = false; + + each(explode(s.browsers), function(v) { + switch (v) { + case 'ie': + case 'msie': + if (tinymce.isIE) + l = true; + break; + + case 'gecko': + if (tinymce.isGecko) + l = true; + break; + + case 'safari': + case 'webkit': + if (tinymce.isWebKit) + l = true; + break; + + case 'opera': + if (tinymce.isOpera) + l = true; + + break; + } + }); + + // Not a valid one + if (!l) + return; + } + + 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 { + c = 0; + + each(document.forms, function(f) { + each(f.elements, function(e) { + if (e.name === v) { + v = 'mce_editor_' + c; + 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. + * + * @param {String} id Editor instance id to return. + * @return {tinymce.Editor} Editor instance to return. + */ + get : function(id) { + return this.editors[id]; + }, + + /** + * Returns a editor instance by id. This method was added for compatibility with the 2.x branch. + * + * @param {String} id Editor instance id to return. + * @return {tinymce.Editor} Editor instance to return. + */ + getInstanceById : function(id) { + return this.get(id); + }, + + /** + * Adds an editor instance to the editor collection. This will also set it as the active editor. + * + * @param {tinymce.Editor} e Editor instance to add to the collection. + * @return {tinymce.Editor} The same instance that got passed in. + */ + add : function(e) { + this.editors[e.id] = e; + this._setActive(e); + + return e; + }, + + /** + * Removes a editor instance from the collection. + * + * @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(e) { + var t = this; + + // Not in the collection + if (!t.editors[e.id]) + return null; + + delete t.editors[e.id]; + + // Select another editor since the active one was removed + if (t.activeEditor == e) { + t._setActive(null); + + each(t.editors, function(e) { + t._setActive(e); + return false; // Break + }); + } + + e.destroy(); + + return e; + }, + + /** + * Executes a specific command on the currently active editor. + * + * @param {String} c Command to perform for example Bold. + * @param {bool} 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 {bool} 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. + * + * @param {String} id Editor id to perform the command on. + * @param {String} c Command to perform for example Bold. + * @param {bool} 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 {bool} 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. + */ + triggerSave : function() { + each(this.editors, function(e) { + e.save(); + }); + }, + + /** + * Adds a language pack, this gets called by the loaded language files like en.js. + * + * @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(e) { + this.selectedInstance = this.activeEditor = e; + } + + /**#@-*/ + }); + + tinymce.EditorManager.preInit(); +})(tinymce); + +// Short for editor manager window.tinyMCE is needed when TinyMCE gets loaded though a XHR call +var tinyMCE = window.tinyMCE = tinymce.EditorManager; diff --git a/js/tiny_mce/classes/ForceBlocks.js b/js/tiny_mce/classes/ForceBlocks.js new file mode 100644 index 0000000000..c0a4969781 --- /dev/null +++ b/js/tiny_mce/classes/ForceBlocks.js @@ -0,0 +1,644 @@ +/** + * $Id: ForceBlocks.js 1137 2009-05-22 15:13:40Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + // Shorten names + var Event, isIE, isGecko, isOpera, each, extend; + + Event = tinymce.dom.Event; + isIE = tinymce.isIE; + isGecko = tinymce.isGecko; + isOpera = tinymce.isOpera; + each = tinymce.each; + extend = tinymce.extend; + + 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(/[ \t\r\n]+/g, '') == ''; + }; + + /** + * 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, '' + elm + '>'); + + o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0' + elm + '>'); + + if (!isIE && !isOpera && o.set) { + // Use instead of BR in padded paragraphs + o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2>' + elm + '>'); + o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2>' + elm + '>'); + } else + o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0' + elm + '>'); + }; + + 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; + + // Force root blocks when typing and when getting output + if (s.forced_root_block) { + 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, s = ed.selection; + + if (e.keyCode == 13 && s.getNode().nodeName != 'LI') { + s.setContent(' ', {format : 'raw'}); + n = ed.dom.get('__'); + n.removeAttribute('id'); + s.select(n); + s.collapse(); + return Event.cancel(e); + } + }); + } + + return; + } + + if (!isIE && s.force_p_newlines) { +/* ed.onPreProcess.add(function(ed, o) { + each(ed.dom.select('br', o.node), function(n) { + var p = n.parentNode; + + // Replace
_dlg.js lang pack file. + */ + 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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + */ + 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 diff --git a/js/tiny_mce/classes/UndoManager.js b/js/tiny_mce/classes/UndoManager.js new file mode 100644 index 0000000000..2a94c7a0ae --- /dev/null +++ b/js/tiny_mce/classes/UndoManager.js @@ -0,0 +1,183 @@ +/** + * $Id: UndoManager.js 1045 2009-03-04 20:03:18Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + /**#@+ + * @class 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. + * @member tinymce.UndoManager + */ + tinymce.create('tinymce.UndoManager', { + index : 0, + data : null, + typing : 0, + + /** + * Constructs a new UndoManager instance. + * + * @constructor + * @param {tinymce.Editor} ed Editor instance to undo/redo in. + */ + UndoManager : function(ed) { + var t = this, Dispatcher = tinymce.util.Dispatcher; + + t.editor = ed; + t.data = []; + t.onAdd = new Dispatcher(this); + t.onUndo = new Dispatcher(this); + t.onRedo = new Dispatcher(this); + }, + + /**#@+ + * @method + */ + + /** + * Adds a new undo level/snapshot to the undo list. + * + * @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(l) { + var t = this, i, ed = t.editor, b, s = ed.settings, la; + + l = l || {}; + l.content = l.content || ed.getContent({format : 'raw', no_events : 1}); + + // Add undo level if needed + l.content = l.content.replace(/^\s*|\s*$/g, ''); + la = t.data[t.index > 0 && (t.index == 0 || t.index == t.data.length) ? t.index - 1 : t.index]; + if (!l.initial && la && l.content == la.content) + return null; + + // Time to compress + if (s.custom_undo_redo_levels) { + if (t.data.length > s.custom_undo_redo_levels) { + for (i = 0; i < t.data.length - 1; i++) + t.data[i] = t.data[i + 1]; + + t.data.length--; + t.index = t.data.length; + } + } + + if (s.custom_undo_redo_restore_selection && !l.initial) + l.bookmark = b = l.bookmark || ed.selection.getBookmark(); + + if (t.index < t.data.length) + t.index++; + + // Only initial marked undo levels should be allowed as first item + // This to workaround a bug with Firefox and the blur event + if (t.data.length === 0 && !l.initial) + return null; + + // Add level + t.data.length = t.index + 1; + t.data[t.index++] = l; + + if (l.initial) + t.index = 0; + + // Set initial bookmark use first real undo level + if (t.data.length == 2 && t.data[0].initial) + t.data[0].bookmark = b; + + t.onAdd.dispatch(t, l); + ed.isNotDirty = 0; + + //console.dir(t.data); + + return l; + }, + + /** + * Undoes the last action. + * + * @return {Object} Undo level or null if no undo was performed. + */ + undo : function() { + var t = this, ed = t.editor, l = l, i; + + if (t.typing) { + t.add(); + t.typing = 0; + } + + if (t.index > 0) { + // If undo on last index then take snapshot + if (t.index == t.data.length && t.index > 1) { + i = t.index; + t.typing = 0; + + if (!t.add()) + t.index = i; + + --t.index; + } + + l = t.data[--t.index]; + ed.setContent(l.content, {format : 'raw'}); + ed.selection.moveToBookmark(l.bookmark); + + t.onUndo.dispatch(t, l); + } + + return l; + }, + + /** + * Redoes the last action. + * + * @return {Object} Redo level or null if no redo was performed. + */ + redo : function() { + var t = this, ed = t.editor, l = null; + + if (t.index < t.data.length - 1) { + l = t.data[++t.index]; + ed.setContent(l.content, {format : 'raw'}); + ed.selection.moveToBookmark(l.bookmark); + + t.onRedo.dispatch(t, l); + } + + return l; + }, + + /** + * Removes all undo levels. + */ + clear : function() { + var t = this; + + t.data = []; + t.index = 0; + t.typing = 0; + t.add({initial : true}); + }, + + /** + * Returns true/false if the undo manager has any undo levels. + * + * @return {bool} true/false if the undo manager has any undo levels. + */ + hasUndo : function() { + return this.index != 0 || this.typing; + }, + + /** + * Returns true/false if the undo manager has any redo levels. + * + * @return {bool} true/false if the undo manager has any redo levels. + */ + hasRedo : function() { + return this.index < this.data.length - 1; + } + + /**#@-*/ + }); +})(tinymce); diff --git a/js/tiny_mce/classes/WindowManager.js b/js/tiny_mce/classes/WindowManager.js new file mode 100644 index 0000000000..eed80232e5 --- /dev/null +++ b/js/tiny_mce/classes/WindowManager.js @@ -0,0 +1,169 @@ +/** + * $Id: WindowManager.js 1045 2009-03-04 20:03:18Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; + + /**#@+ + * @class This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs. + * @member tinymce.WindowManager + */ + tinymce.create('tinymce.WindowManager', { + /** + * Constructs a new window manager instance. + * + * @constructor + * @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 = {}; + }, + + /**#@+ + * @method + */ + + /** + * Opens a new window. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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); + }, + + // 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 new file mode 100644 index 0000000000..e5498bb081 --- /dev/null +++ b/js/tiny_mce/classes/adapter/jquery/adapter.js @@ -0,0 +1,240 @@ +/** + * $Id: adapter.js 1167 2009-06-29 13:07:20Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + * + * This file contains all adapter logic needed to use jQuery as the base API for TinyMCE. + */ + +// #ifdef jquery_adapter + +(function($, tinymce) { + var is = tinymce.is; + + if (!window.jQuery) + return alert("Load jQuery first!"); + + // 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 || []);}, + 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]); + }; +})(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 new file mode 100644 index 0000000000..c315137651 --- /dev/null +++ b/js/tiny_mce/classes/adapter/jquery/jquery.tinymce.js @@ -0,0 +1,179 @@ +/** + * $Id: jquery.uri.js 453 2008-10-14 12:24:41Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function($) { + var lazyLoading, delayedInits = []; + + function patch(type, name, patch_func) { + var func; + + func = $.fn[name]; + + $.fn[name] = function() { + var val; + + if (type !== 'after') { + val = patch_func.apply(this, arguments); + + // We got a return value pass out that instead + if (val !== undefined) + return val; + } + + val = func.apply(this, arguments); + + if (type !== 'before') + patch_func.apply(this, arguments); + + return val; + }; + }; + + $.fn.tinymce = function(settings) { + var t = this, url, suffix = '', ed; + + // No match then just ignore the call + if (!t.length) + return; + + // Get editor instance + if (!settings) + return tinyMCE.get(this[0].id); + + function init() { + // Apply patches once + if (applyPatch) { + applyPatch(); + applyPatch = null; + } + + // Create an editor instance for each matched node + t.each(function(i, n) { + var ed, id = n.id || tinymce.DOM.uniqueId(); + + n.id = id; + ed = new tinymce.Editor(id, settings); + + ed.render(); + }); + }; + + // Load TinyMCE on demand + if (!window['tinymce'] && !lazyLoading && (url = settings.script_url)) { + lazyLoading = 1; + + if (/_(src|dev)\.js/g.test(url)) + suffix = '_src'; + + window.tinyMCEPreInit = { + base : url.substring(0, url.lastIndexOf('/')), + suffix : suffix, + query : '' + }; + + $.getScript(url, function() { + // Script is loaded time to initialize TinyMCE + tinymce.dom.Event.domLoaded = 1; + lazyLoading = 2; + init(); + + $.each(delayedInits, function(i, init) { + init(); + }); + }); + } else { + if (lazyLoading === 1) + delayedInits.push(init); + else + init(); + } + }; + + // Add :tinymce psuedo selector + $.extend($.expr[':'], { + tinymce : function(e) { + return e.id && !!tinyMCE.get(e.id); + } + }); + + function applyPatch() { + function removeEditors() { + this.find('span.mceEditor,div.mceEditor').each(function(i, n) { + var ed; + + if (ed = tinyMCE.get(n.id.replace(/_parent$/, ''))) { + ed.remove(); + } + }); + }; + + function loadOrSave(value) { + var ed; + + // Handle set value + if (value !== undefined) { + removeEditors.call(this); + + // Saves the contents before get/set value of textarea/div + this.each(function(i, node) { + var ed; + + if (ed = tinyMCE.get(node.id)) + ed.setContent(value); + }); + } else if (this.length > 0) { + // Handle get value + if (ed = tinyMCE.get(this[0].id)) + return ed.getContent(); + } + }; + + // Patch various jQuery functions + patch("both", 'text', function(value) { + // Text encode value + if (value !== undefined) + return loadOrSave.call(this, value); + + // Get contents as plain text + if (this.length > 0) { + // Handle get value + if (ed = tinyMCE.get(this[0].id)) + return ed.getContent().replace(/<[^>]+>/g, ''); + } + }); + + $.each(['val', 'html'], function(i, name) { + patch("both", name, loadOrSave); + }); + + $.each(['append', 'prepend'], function(i, name) { + patch("before", name, function(value) { + if (value !== undefined) { + this.each(function(i, node) { + var ed; + + if (ed = tinyMCE.get(node.id)) { + if (name === 'append') + ed.setContent(ed.getContent() + value); + else + ed.setContent(value + ed.getContent()); + } + }); + } + }); + }); + + patch("both", 'attr', function(name, value) { + if (name && name === 'value') + return loadOrSave.call(this, value); + }); + + $.each(['remove', 'replaceWith', 'replaceAll', 'empty'], function(i, name) { + patch("before", name, removeEditors); + }); + }; +})(jQuery); \ No newline at end of file diff --git a/js/tiny_mce/classes/adapter/prototype/adapter.js b/js/tiny_mce/classes/adapter/prototype/adapter.js new file mode 100644 index 0000000000..51822dfaf8 --- /dev/null +++ b/js/tiny_mce/classes/adapter/prototype/adapter.js @@ -0,0 +1,38 @@ +/** + * $Id: adapter.js 1000 2009-02-12 13:03:40Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + * + * This file contains all adapter logic needed to use prototype library as the base API for TinyMCE. + */ + +// #ifdef prototype_adapter + +(function() { + if (!window.Prototype) + return alert("Load prototype first!"); + + // Patch in core NS functions + tinymce.extend(tinymce, { + trim : function(s) {return s ? s.strip() : '';}, + inArray : function(a, v) {return a && a.indexOf ? a.indexOf(v) : -1;} + }); + + // Patch in functions in various clases + // Add a "#ifndefjquery" statement around each core API function you add below + var patches = { + 'tinymce.util.JSON' : { + serialize : function(o) { + return o.toJSON(); + } + }, + }; + + // Patch functions after a class is created + tinymce.onCreate = function(ty, c, p) { + tinymce.extend(p, patches[c]); + }; +})(); + +// #endif diff --git a/js/tiny_mce/classes/commands/BlockQuote.js b/js/tiny_mce/classes/commands/BlockQuote.js new file mode 100644 index 0000000000..44ef9b1243 --- /dev/null +++ b/js/tiny_mce/classes/commands/BlockQuote.js @@ -0,0 +1,135 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + tinymce.GlobalCommands.add('mceBlockQuote', function() { + var ed = this, s = ed.selection, dom = ed.dom, sb, eb, n, bm, bq, r, bq2, i, nl; + + function getBQ(e) { + return dom.getParent(e, function(n) {return n.nodeName === 'BLOCKQUOTE';}); + }; + + // Get start/end block + sb = dom.getParent(s.getStart(), dom.isBlock); + eb = dom.getParent(s.getEnd(), dom.isBlock); + + // Remove blockquote(s) + if (bq = getBQ(sb)) { + if (sb != eb || sb.childNodes.length > 1 || (sb.childNodes.length == 1 && sb.firstChild.nodeName != 'BR')) + bm = s.getBookmark(); + + // Move all elements after the end block into new bq + if (getBQ(eb)) { + bq2 = bq.cloneNode(false); + + while (n = eb.nextSibling) + bq2.appendChild(n.parentNode.removeChild(n)); + } + + // Add new bq after + if (bq2) + dom.insertAfter(bq2, bq); + + // Move all selected blocks after the current bq + nl = s.getSelectedBlocks(sb, eb); + for (i = nl.length - 1; i >= 0; i--) { + dom.insertAfter(nl[i], bq); + } + + // Empty bq, then remove it + if (/^\s*$/.test(bq.innerHTML)) + dom.remove(bq, 1); // Keep children so boomark restoration works correctly + + // Empty bq, then remote it + if (bq2 && /^\s*$/.test(bq2.innerHTML)) + dom.remove(bq2, 1); // Keep children so boomark restoration works correctly + + if (!bm) { + // Move caret inside empty block element + if (!tinymce.isIE) { + r = ed.getDoc().createRange(); + r.setStart(sb, 0); + r.setEnd(sb, 0); + s.setRng(r); + } else { + s.select(sb); + s.collapse(0); + + // IE misses the empty block some times element so we must move back the caret + if (dom.getParent(s.getStart(), dom.isBlock) != sb) { + r = s.getRng(); + r.move('character', -1); + r.select(); + } + } + } else + ed.selection.moveToBookmark(bm); + + return; + } + + // Since IE can start with a totally empty document we need to add the first bq and paragraph + if (tinymce.isIE && !sb && !eb) { + ed.getDoc().execCommand('Indent'); + n = getBQ(s.getNode()); + n.style.margin = n.dir = ''; // IE adds margin and dir to bq + return; + } + + if (!sb || !eb) + return; + + // If empty paragraph node then do not use bookmark + if (sb != eb || sb.childNodes.length > 1 || (sb.childNodes.length == 1 && sb.firstChild.nodeName != 'BR')) + bm = s.getBookmark(); + + // Move selected block elements into a bq + tinymce.each(s.getSelectedBlocks(getBQ(s.getStart()), getBQ(s.getEnd())), function(e) { + // Found existing BQ add to this one + if (e.nodeName == 'BLOCKQUOTE' && !bq) { + bq = e; + return; + } + + // No BQ found, create one + if (!bq) { + bq = dom.create('blockquote'); + e.parentNode.insertBefore(bq, e); + } + + // Add children from existing BQ + if (e.nodeName == 'BLOCKQUOTE' && bq) { + n = e.firstChild; + + while (n) { + bq.appendChild(n.cloneNode(true)); + n = n.nextSibling; + } + + dom.remove(e); + return; + } + + // Add non BQ element to BQ + bq.appendChild(dom.remove(e)); + }); + + if (!bm) { + // Move caret inside empty block element + if (!tinymce.isIE) { + r = ed.getDoc().createRange(); + r.setStart(sb, 0); + r.setEnd(sb, 0); + s.setRng(r); + } else { + s.select(sb); + s.collapse(1); + } + } else + s.moveToBookmark(bm); + }); +})(tinymce); diff --git a/js/tiny_mce/classes/commands/CutCopyPaste.js b/js/tiny_mce/classes/commands/CutCopyPaste.js new file mode 100644 index 0000000000..886842ddc1 --- /dev/null +++ b/js/tiny_mce/classes/commands/CutCopyPaste.js @@ -0,0 +1,24 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + tinymce.each(['Cut', 'Copy', 'Paste'], function(cmd) { + tinymce.GlobalCommands.add(cmd, function() { + var ed = this, doc = ed.getDoc(); + + try { + doc.execCommand(cmd, false, null); + + // On WebKit the command will just be ignored if it's not enabled + if (!doc.queryCommandSupported(cmd)) + throw 'Error'; + } catch (ex) { + ed.windowManager.alert(ed.getLang('clipboard_no_support')); + } + }); + }); +})(tinymce); diff --git a/js/tiny_mce/classes/commands/InsertHorizontalRule.js b/js/tiny_mce/classes/commands/InsertHorizontalRule.js new file mode 100644 index 0000000000..02542aa85f --- /dev/null +++ b/js/tiny_mce/classes/commands/InsertHorizontalRule.js @@ -0,0 +1,15 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + tinymce.GlobalCommands.add('InsertHorizontalRule', function() { + if (tinymce.isOpera) + return this.getDoc().execCommand('InsertHorizontalRule', false, ''); + + this.selection.setContent(''); + }); +})(tinymce); diff --git a/js/tiny_mce/classes/commands/RemoveFormat.js b/js/tiny_mce/classes/commands/RemoveFormat.js new file mode 100644 index 0000000000..6a43902f5c --- /dev/null +++ b/js/tiny_mce/classes/commands/RemoveFormat.js @@ -0,0 +1,173 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + function processRange(dom, start, end, callback) { + var ancestor, n, startPoint, endPoint, sib; + + function findEndPoint(n, c) { + do { + if (n.parentNode == c) + return n; + + n = n.parentNode; + } while(n); + }; + + function process(n) { + callback(n); + tinymce.walk(n, callback, 'childNodes'); + }; + + // Find common ancestor and end points + ancestor = dom.findCommonAncestor(start, end); + startPoint = findEndPoint(start, ancestor) || start; + endPoint = findEndPoint(end, ancestor) || end; + + // Process left leaf + for (n = start; n && n != startPoint; n = n.parentNode) { + for (sib = n.nextSibling; sib; sib = sib.nextSibling) + process(sib); + } + + // Process middle from start to end point + if (startPoint != endPoint) { + for (n = startPoint.nextSibling; n && n != endPoint; n = n.nextSibling) + process(n); + } else + process(startPoint); + + // Process right leaf + for (n = end; n && n != endPoint; n = n.parentNode) { + for (sib = n.previousSibling; sib; sib = sib.previousSibling) + process(sib); + } + }; + + tinymce.GlobalCommands.add('RemoveFormat', function() { + var ed = this, dom = ed.dom, s = ed.selection, r = s.getRng(1), nodes = [], bm, start, end, sc, so, ec, eo, n; + + function findFormatRoot(n) { + var sp; + + dom.getParent(n, function(n) { + if (dom.is(n, ed.getParam('removeformat_selector'))) + sp = n; + + return dom.isBlock(n); + }, ed.getBody()); + + return sp; + }; + + function collect(n) { + if (dom.is(n, ed.getParam('removeformat_selector'))) + nodes.push(n); + }; + + function walk(n) { + collect(n); + tinymce.walk(n, collect, 'childNodes'); + }; + + bm = s.getBookmark(); + sc = r.startContainer; + ec = r.endContainer; + so = r.startOffset; + eo = r.endOffset; + sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc; + ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec; + + // Same container + if (sc == ec) { // TEXT_NODE + start = findFormatRoot(sc); + + // Handle single text node + if (sc.nodeType == 3) { + if (start && start.nodeType == 1) { // ELEMENT + n = sc.splitText(so); + n.splitText(eo - so); + dom.split(start, n); + + s.moveToBookmark(bm); + } + + return; + } + + // Handle single element + walk(dom.split(start, sc) || sc); + } else { + // Find start/end format root + start = findFormatRoot(sc); + end = findFormatRoot(ec); + + // Split start text node + if (start) { + if (sc.nodeType == 3) { // TEXT + // Since IE doesn't support white space nodes in the DOM we need to + // add this invisible character so that the splitText function can split the contents + if (so == sc.nodeValue.length) + sc.nodeValue += '\uFEFF'; // Yet another pesky IE fix + + sc = sc.splitText(so); + } + } + + // Split end text node + if (end) { + if (ec.nodeType == 3) // TEXT + ec.splitText(eo); + } + + // If the start and end format root is the same then we need to wrap + // the end node in a span since the split calls might change the reference + // Example: x[yz---12]3 + if (start && start == end) + dom.replace(dom.create('span', {id : '__end'}, ec.cloneNode(true)), ec); + + // Split all start containers down to the format root + if (start) + start = dom.split(start, sc); + else + start = sc; + + // If there is a span wrapper use that one instead + if (n = dom.get('__end')) { + ec = n; + end = findFormatRoot(ec); + } + + // Split all end containers down to the format root + if (end) + end = dom.split(end, ec); + else + end = ec; + + // Collect nodes in between + processRange(dom, start, end, collect); + + // Remove invisible character for IE workaround if we find it + if (sc.nodeValue == '\uFEFF') + sc.nodeValue = ''; + + // Process start/end container elements + walk(ec); + walk(sc); + } + + // Remove all collected nodes + tinymce.each(nodes, function(n) { + dom.remove(n, 1); + }); + + // Remove leftover wrapper + dom.remove('__end', 1); + + s.moveToBookmark(bm); + }); +})(tinymce); diff --git a/js/tiny_mce/classes/commands/UndoRedo.js b/js/tiny_mce/classes/commands/UndoRedo.js new file mode 100644 index 0000000000..b2745e1e38 --- /dev/null +++ b/js/tiny_mce/classes/commands/UndoRedo.js @@ -0,0 +1,38 @@ +/** + * $Id: EditorCommands.js 1042 2009-03-04 16:00:50Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function() { + var cmds = tinymce.GlobalCommands; + + cmds.add(['mceEndUndoLevel', 'mceAddUndoLevel'], function() { + this.undoManager.add(); + }); + + cmds.add('Undo', function() { + var ed = this; + + if (ed.settings.custom_undo_redo) { + ed.undoManager.undo(); + ed.nodeChanged(); + return true; + } + + return false; // Run browser command + }); + + cmds.add('Redo', function() { + var ed = this; + + if (ed.settings.custom_undo_redo) { + ed.undoManager.redo(); + ed.nodeChanged(); + return true; + } + + return false; // Run browser command + }); +})(); diff --git a/js/tiny_mce/classes/dom/DOMUtils.js b/js/tiny_mce/classes/dom/DOMUtils.js new file mode 100644 index 0000000000..8f07a4a297 --- /dev/null +++ b/js/tiny_mce/classes/dom/DOMUtils.js @@ -0,0 +1,1823 @@ +/** + * $Id: DOMUtils.js 1154 2009-06-10 17:31:03Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + // Shorten names + var each = tinymce.each, is = tinymce.is; + var isWebKit = tinymce.isWebKit, isIE = tinymce.isIE; + + /**#@+ + * @class Utility class for various DOM manipulation and retrival functions. + * @member 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 + * @param {Document} d Document reference to bind the utility class to. + * @param {settings} s Optional settings collection. + */ + DOMUtils : function(d, s) { + var t = this; + + 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; + } + } + + tinymce.addUnload(t.destroy, t); + }, + + /**#@+ + * @method + */ + + /** + * 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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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; + }, + + // #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. + * + * @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. + * + * @param {Node/NodeList} n DOM node to match or an array of nodes to match. + * @param {String} patt CSS pattern to match the element agains. + */ + is : function(n, patt) { + return tinymce.dom.Sizzle.matches(patt, n.nodeType ? [n] : n).length > 0; + }, + + // #endif + + /** + * Adds the specified element to another element or elements. + * + * @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 {bool} 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. + * + * @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. + * + * @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 + '' + n + '>'; + + return o + ' />'; + }, + + /** + * Removes/deletes the specified element(s) from the DOM. + * + * @param {String/Element/Array} n ID of element or DOM element object or array containing multiple elements/ids. + * @param {bool} k 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(n, k) { + var t = this; + + return this.run(n, function(n) { + var p, g, i; + + p = n.parentNode; + + if (!p) + return null; + + if (k) { + for (i = n.childNodes.length - 1; i >= 0; i--) + t.insertAfter(n.childNodes[i], n); + + //each(n.childNodes, function(c) { + // p.insertBefore(c.cloneNode(true), n); + //}); + } + + // Fix IE psuedo leak + if (t.fixPsuedoLeaks) { + p = n.cloneNode(true); + k = 'IELeakGarbageBin'; + g = t.get(k) || t.add(t.doc.body, 'div', {id : k, style : 'display:none'}); + g.appendChild(n); + g.innerHTML = ''; + + return p; + } + + return p.removeChild(n); + }); + }, + + /** + * Sets the CSS style value on a HTML element. The name can be a camelcase string + * or the CSS style name like background-color. + * + * @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. + * + * @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). + * + * @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. + * + * @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. + * + * @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. + * + * @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); + + if (n === 'style') { + v = v || e.style.cssText; + + if (v) { + v = t.serializeStyle(t.parseStyle(v)); + + 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. + * + * @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. + * + * @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. + * + * @param {Object} o Object to serialize as string for example: {border : '1px solid red'} + * @return {String} String representation of the style object for example: border: 1px solid red. + */ + serializeStyle : function(o) { + var s = ''; + + each(o, function(v, k) { + if (k && v) { + if (tinymce.isGecko && k.indexOf('-moz-') === 0) + return; + + switch (k) { + case 'color': + case 'background-color': + v = v.toLowerCase(); + break; + } + + s += (s ? ' ' : '') + k + ': ' + v + ';'; + } + }); + + return s; + }, + + /** + * Imports/loads the specified CSS file into the document bound to the class. + * + * @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. + * + * @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. + * + * @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, ' '); + + return e.className = tinymce.trim(v != ' ' ? v : ''); + } + + return e.className; + }); + }, + + /** + * Returns true if the specified element has the specified class. + * + * @param {String/Element} n HTML element or element id string to check CSS class on. + * @param {String] c CSS class to check for. + * @return {bool} 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. + * + * @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. + * + * @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. + * + * @param {String/Element} e Id or element to check display state on. + * @return {bool} 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. + * + * @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. + * + * @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() { + 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 + + // Remove all child nodes + while (e.firstChild) + e.firstChild.removeNode(); + + // 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(/]+)>|/g, ''); + h = h.replace(/<\/p>/g, ''); + + // Set the new HTML with DIVs + set(); + + // Replace all DIV elements with he 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, '<$1b$2>'); + h = h.replace(/<(\/?)em>|]+)>/gi, '<$1i$2>'); + } else if (isIE) { + h = h.replace(/'/g, '''); // IE can't handle apos + h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct + } + + // Fix some issues + h = h.replace(/]+)\/>|/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 (/'); + }; + + if (!tinymce.is(u, 'string')) { + each(u, function(u) { + loadScript(u); + }); + + if (cb) + cb.call(s || t); + } else { + loadScript(u); + + if (cb) + cb.call(s || t); + } + }, + + /** + * Starts the loading of the queue. + * + * @param {function} cb Optional callback to execute when all queued items are loaded. + * @param {Object} s Optional scope to execute the callback in. + */ + loadQueue : function(cb, s) { + var t = this; + + if (!t.queueLoading) { + t.queueLoading = 1; + t.queueCallbacks = []; + + t.loadScripts(t.queue, function() { + t.queueLoading = 0; + + if (cb) + cb.call(s || t); + + each(t.queueCallbacks, function(o) { + o.func.call(o.scope); + }); + }); + } else if (cb) + t.queueCallbacks.push({func : cb, scope : s || t}); + }, + + /** + * Evaluates the specified string inside the global namespace/window scope. + * + * @param {string} Script contents to evaluate. + */ + eval : function(co) { + var w = window; + + // Evaluate script + if (!w.execScript) { + try { + eval.call(w, co); + } catch (ex) { + eval(co, w); // Firefox 3.0a8 + } + } else + w.execScript(co); // IE + }, + + /** + * 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. + * + * @param {Array} sc Array of queue items to load. + * @param {function} cb Optional callback to execute ones all items are loaded. + * @param {Object} s Optional scope to execute callback in. + */ + loadScripts : function(sc, cb, s) { + var t = this, lo = t.lookup; + + function done(o) { + o.state = 2; // Has been loaded + + // Run callback + if (o.func) + o.func.call(o.scope || t); + }; + + function allDone() { + var l; + + // Check if all files are loaded + l = sc.length; + each(sc, function(o) { + o = lo[o.url]; + + if (o.state === 2) {// It has finished loading + done(o); + l--; + } else + load(o); + }); + + // They are all loaded + if (l === 0 && cb) { + cb.call(s || t); + cb = 0; + } + }; + + function load(o) { + if (o.state > 0) + return; + + o.state = 1; // Is loading + + tinymce.dom.ScriptLoader.loadScript(o.url, function() { + done(o); + allDone(); + }); + + /* + tinymce.util.XHR.send({ + url : o.url, + error : t.settings.error, + success : function(co) { + t.eval(co); + done(o); + allDone(); + } + }); + */ + }; + + each(sc, function(o) { + var u = o.url; + + // Add to queue if needed + if (!lo[u]) { + lo[u] = o; + t.queue.push(o); + } else + o = lo[u]; + + // Is already loading or has been loaded + if (o.state > 0) + return; + + if (!Event.domLoaded && !t.settings.strict_mode) { + var ix, ol = ''; + + // Add onload events + if (cb || o.func) { + o.state = 1; // Is loading + + ix = tinymce.dom.ScriptLoader._addOnLoad(function() { + done(o); + allDone(); + }); + + if (tinymce.isIE) + ol = ' onreadystatechange="'; + else + ol = ' onload="'; + + ol += 'tinymce.dom.ScriptLoader._onLoad(this,\'' + u + '\',' + ix + ');"'; + } + + document.write(''); + + if (!o.func) + done(o); + } else + load(o); + }); + + allDone(); + }, + + // Static methods + 'static' : { + _addOnLoad : function(f) { + var t = this; + + t._funcs = t._funcs || []; + t._funcs.push(f); + + return t._funcs.length - 1; + }, + + _onLoad : function(e, u, ix) { + if (!tinymce.isIE || e.readyState == 'complete') + this._funcs[ix].call(this); + }, + + /** + * Loads the specified script without adding it to any load queue. + * + * @param {string} u URL to dynamically load. + * @param {function} cb Callback function to executed on load. + */ + loadScript : function(u, cb) { + var id = tinymce.DOM.uniqueId(), e; + + function done() { + Event.clear(id); + tinymce.DOM.remove(id); + + if (cb) { + cb.call(document, u); + cb = 0; + } + }; + + if (tinymce.isIE) { +/* Event.add(e, 'readystatechange', function(e) { + if (e.target && e.target.readyState == 'complete') + done(); + });*/ + + tinymce.util.XHR.send({ + url : tinymce._addVer(u), + async : false, + success : function(co) { + window.execScript(co); + done(); + } + }); + } else { + e = tinymce.DOM.create('script', {id : id, type : 'text/javascript', src : tinymce._addVer(u)}); + Event.add(e, 'load', done); + + // Check for head or body + (document.getElementsByTagName('head')[0] || document.body).appendChild(e); + } + } + } + + /**#@-*/ + }); + + // 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 new file mode 100644 index 0000000000..87c0a76fda --- /dev/null +++ b/js/tiny_mce/classes/dom/Selection.js @@ -0,0 +1,747 @@ +/** + * $Id: Selection.js 1149 2009-06-01 11:47:08Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(function(tinymce) { + function trimNl(s) { + return s.replace(/[\n\r]+/g, ''); + }; + + // Shorten names + var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; + + /**#@+ + * @class 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. + * @member tinymce.dom.Selection + */ + tinymce.create('tinymce.dom.Selection', { + /** + * Constructs a new selection instance. + * + * @constructor + * @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); + }, + + /**#@+ + * @method + */ + + /** + * Returns the selected contents using the DOM serializer passed in to this class. + * + * @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. + * + * @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 + 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.setEndAfter(c); + t.setRng(r); + + // Delete the marker, and hopefully the caret gets placed in the right location + // Removed this since it seems to remove in FF and simply deleting it + // doesn't seem to affect the caret position in any browser + //d.execCommand('Delete', false, null); + + // 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. + * + * @return {Element} Start element of selection range. + */ + getStart : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(1); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.firstChild; + + return e; + } else { + e = r.startContainer; + + if (e.nodeName == 'BODY') + return e.firstChild; + + return t.dom.getParent(e, '*'); + } + }, + + /** + * Returns the end element of a selection range. If the end is in a text + * node the parent element will be returned. + * + * @return {Element} End element of selection range. + */ + getEnd : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild; + + return e; + } else { + e = r.endContainer; + + if (e.nodeName == 'BODY') + return e.lastChild; + + return t.dom.getParent(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. + * + * @param {bool} si Optional state if the bookmark should be simple or not. Default is complex. + * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection. + */ + getBookmark : function(si) { + var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv; + sx = vp.x; + sy = vp.y; + + // Simple bookmark fast but not as persistent + if (si) + return {rng : r, scrollX : sx, scrollY : sy}; + + // Handle IE + if (isIE) { + // Control selection + if (r.item) { + e = r.item(0); + + each(t.dom.select(e.nodeName), function(n, i) { + if (e == n) { + sp = i; + return false; + } + }); + + return { + tag : e.nodeName, + index : sp, + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + tr = t.dom.doc.body.createTextRange(); + tr.moveToElementText(ro); + tr.collapse(true); + bp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(true); + sp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(false); + le = Math.abs(tr.move('character', c)) - sp; + + return { + start : sp - bp, + length : le, + scrollX : sx, + scrollY : sy + }; + } + + // Handle W3C + e = t.getNode(); + s = t.getSel(); + + if (!s) + return null; + + // Image selection + if (e && e.nodeName == 'IMG') { + return { + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + + function getPos(r, sn, en) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; + + while ((n = w.nextNode()) != null) { + if (n == sn) + d.start = p; + + if (n == en) { + d.end = p; + return d; + } + + p += trimNl(n.nodeValue || '').length; + } + + return null; + }; + + // Caret or selection + if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) { + e = getPos(ro, s.anchorNode, s.focusNode); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + // Count whitespace before + trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + + return { + start : Math.max(e.start + s.anchorOffset - wb, 0), + end : Math.max(e.end + s.focusOffset - wb, 0), + scrollX : sx, + scrollY : sy, + beg : s.anchorOffset - wb == 0 + }; + } else { + e = getPos(ro, r.startContainer, r.endContainer); + + // Count whitespace before start and end container + //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;}); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + return { + start : Math.max(e.start + r.startOffset - wb, 0), + end : Math.max(e.end + r.endOffset - wa, 0), + scrollX : sx, + scrollY : sy, + beg : r.startOffset - wb == 0 + }; + } + }, + + /** + * Restores the selection to the specified bookmark. + * + * @param {Object} bookmark Bookmark to restore selection from. + * @return {bool} true/false if it was successful or not. + */ + moveToBookmark : function(b) { + var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv; + + function getPos(r, sp, ep) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb; + + while ((n = w.nextNode()) != null) { + wa = wb = 0; + + nv = n.nodeValue || ''; + //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;}); + //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;}); + + nvl = trimNl(nv).length; + p += nvl; + + if (p >= sp && !d.startNode) { + o = sp - (p - nvl); + + // Fix for odd quirk in FF + if (b.beg && o >= nvl) + continue; + + d.startNode = n; + d.startOffset = o + wb; + } + + if (p >= ep) { + d.endNode = n; + d.endOffset = ep - (p - nvl) + wb; + return d; + } + } + + return null; + }; + + if (!b) + return false; + + t.win.scrollTo(b.scrollX, b.scrollY); + + // Handle explorer + if (isIE) { + // Handle simple + if (r = b.rng) { + try { + r.select(); + } catch (ex) { + // Ignore + } + + return true; + } + + t.win.focus(); + + // Handle control bookmark + if (b.tag) { + r = ro.createControlRange(); + + each(t.dom.select(b.tag), function(n, i) { + if (i == b.index) + r.addElement(n); + }); + } else { + // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs + try { + // Incorrect bookmark + if (b.start < 0) + return true; + + r = s.createRange(); + r.moveToElementText(ro); + r.collapse(true); + r.moveStart('character', b.start); + r.moveEnd('character', b.length); + } catch (ex2) { + return true; + } + } + + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + + return true; + } + + // Handle W3C + if (!s) + return false; + + // Handle simple + if (b.rng) { + s.removeAllRanges(); + s.addRange(b.rng); + } else { + if (is(b.start) && is(b.end)) { + try { + sd = getPos(ro, b.start, b.end); + + if (sd) { + r = t.dom.doc.createRange(); + r.setStart(sd.startNode, sd.startOffset); + r.setEnd(sd.endNode, sd.endOffset); + s.removeAllRanges(); + s.addRange(r); + } + + if (!tinymce.isOpera) + t.win.focus(); + } catch (ex) { + // Ignore + } + } + } + }, + + /** + * Selects the specified element. This will place the start and end of the selection range around the element. + * + * @param {Element} n HMTL DOM element to select. + * @param {} c 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(n, c) { + var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document; + + function find(n, start) { + var walker, o; + + if (n) { + walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + // Find first/last non empty text node + while (n = walker.nextNode()) { + o = n; + + if (tinymce.trim(n.nodeValue).length != 0) { + if (start) + return n; + else + o = n; + } + } + } + + return o; + }; + + if (isIE) { + try { + b = d.body; + + if (/^(IMG|TABLE)$/.test(n.nodeName)) { + r = b.createControlRange(); + r.addElement(n); + } else { + r = b.createTextRange(); + r.moveToElementText(n); + } + + r.select(); + } catch (ex) { + // Throws illigal agrument in IE some times + } + } else { + if (c) { + fn = find(n, 1) || t.dom.select('br:first', n)[0]; + ln = find(n, 0) || t.dom.select('br:last', n)[0]; + + if (fn && ln) { + r = d.createRange(); + + if (fn.nodeName == 'BR') + r.setStartBefore(fn); + else + r.setStart(fn, 0); + + if (ln.nodeName == 'BR') + r.setEndBefore(ln); + else + r.setEnd(ln, ln.nodeValue.length); + } else + r.selectNode(n); + } else + r.selectNode(n); + + t.setRng(r); + } + + return n; + }, + + /** + * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection. + * + * @return {bool} 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; + + return !s || r.boundingWidth == 0 || r.collapsed; + }, + + /** + * Collapse the selection to start or end of range. + * + * @param {bool} 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. + * + * @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. + * + * @param {bool} 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 = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange(); + + return r; + }, + + /** + * Changes the selection to the specified DOM range. + * + * @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. + * + * @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. + * + * @return {Element} Currently selected element or common ancestor element. + */ + getNode : function() { + var t = this, r = t.getRng(), s = t.getSel(), e; + + if (!isIE) { + // Range maybe lost after the editor is made visible again + if (!r) + return t.dom.getRoot(); + + e = r.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!r.collapsed) { + // If the anchor node is a element instead of a text node then return this element + if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1) + return s.anchorNode.childNodes[s.anchorOffset]; + + if (r.startContainer == r.endContainer) { + if (r.startOffset - r.endOffset < 2) { + if (r.startContainer.hasChildNodes()) + e = r.startContainer.childNodes[r.startOffset]; + } + } + } + + return t.dom.getParent(e, '*'); + } + + return r.item ? r.item(0) : r.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); diff --git a/js/tiny_mce/classes/dom/Serializer.js b/js/tiny_mce/classes/dom/Serializer.js new file mode 100644 index 0000000000..65717ecaa5 --- /dev/null +++ b/js/tiny_mce/classes/dom/Serializer.js @@ -0,0 +1,979 @@ +/** + * $Id: Serializer.js 1173 2009-06-29 14:44:25Z spocke $ + * + * @author Moxiecode + * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. + */ + +(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'); + }; + + /**#@+ + * @class 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. + * @member tinymce.dom.Serializer + */ + tinymce.create('tinymce.dom.Serializer', { + /** + * Constucts a new DOM serializer class. + * + * @constructor + * @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', + bool_attrs : /(checked|disabled|readonly|selected|nowrap)/, + valid_elements : '*[*]', + extended_valid_elements : 0, + valid_child_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, + font_size_style_values : 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; + + 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 '' + c + '>'; + + 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 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 ]*>)(.*?)(<\/script>)/g}, + {pattern : /(]*>)(.*?)(<\/noscript>)/g}, + {pattern : /("});j=j.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(h,l,k){return""})}j=j.replace(//g,"");j=j.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi,function(h,l){function k(o,n,q){var p=q;if(h.indexOf("mce_"+n)!=-1){return o}if(n=="style"){if(g._isRes(q)){return o}if(i.hex_colors){p=p.replace(/rgb\([^\)]+\)/g,function(m){return g.toHex(m)})}if(i.url_converter){p=p.replace(/url\([\'\"]?([^\)\'\"]+)\)/g,function(m,r){return"url("+g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(r),n,l))+")"})}}else{if(n!="coords"&&n!="shape"){if(i.url_converter){p=g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(q),n,l))}}}return" "+n+'="'+q+'" mce_'+n+'="'+p+'"'}h=h.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi,k);h=h.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi,k);return h.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi,k)})}return j},getOuterHTML:function(f){var g;f=this.get(f);if(!f){return null}if(f.outerHTML!==undefined){return f.outerHTML}g=(f.ownerDocument||this.doc).createElement("body");g.appendChild(f.cloneNode(true));return g.innerHTML},setOuterHTML:function(i,g,j){var f=this;return this.run(i,function(h){var l,k;h=f.get(h);j=j||h.ownerDocument||f.doc;if(a&&h.nodeType==1){h.outerHTML=g}else{k=j.createElement("body");k.innerHTML=g;l=k.lastChild;while(l){f.insertAfter(l.cloneNode(true),h);l=l.previousSibling}f.remove(h)}})},decode:function(g){var h,i,f;if(/&[^;]+;/.test(g)){h=this.doc.createElement("div");h.innerHTML=g;i=h.firstChild;f="";if(i){do{f+=i.nodeValue}while(i.nextSibling)}return f||g}return g},encode:function(f){return f?(""+f).replace(/[<>&\"]/g,function(h,g){switch(h){case"&":return"&";case'"':return""";case"<":return"<";case">":return">"}return h}):f},insertAfter:function(h,g){var f=this;g=f.get(g);return this.run(h,function(k){var j,i;j=g.parentNode;i=g.nextSibling;if(i){j.insertBefore(k,i)}else{j.appendChild(k)}return k})},isBlock:function(f){if(f.nodeType&&f.nodeType!==1){return false}f=f.nodeName||f;return/^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(f)},replace:function(i,h,f){var g=this;if(b(h,"array")){i=i.cloneNode(true)}return g.run(h,function(j){if(f){e(j.childNodes,function(k){i.appendChild(k.cloneNode(true))})}if(g.fixPsuedoLeaks&&j.nodeType===1){j.parentNode.insertBefore(i,j);g.remove(j);return i}return j.parentNode.replaceChild(i,j)})},findCommonAncestor:function(h,f){var i=h,g;while(i){g=f;while(g&&i!=g){g=g.parentNode}if(i==g){break}i=i.parentNode}if(!i&&h.ownerDocument){return h.ownerDocument.documentElement}return i},toHex:function(f){var h=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(f);function g(i){i=parseInt(i).toString(16);return i.length>1?i:"0"+i}if(h){f="#"+g(h[1])+g(h[2])+g(h[3]);return f}return f},getClasses:function(){var l=this,g=[],k,m={},n=l.settings.class_filter,j;if(l.classes){return l.classes}function o(f){e(f.imports,function(i){o(i)});e(f.cssRules||f.rules,function(i){switch(i.type||1){case 1:if(i.selectorText){e(i.selectorText.split(","),function(p){p=p.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(p)||!/\.[\w\-]+$/.test(p)){return}j=p;p=p.replace(/.*\.([a-z0-9_\-]+).*/i,"$1");if(n&&!(p=n(p,j))){return}if(!m[p]){g.push({"class":p});m[p]=1}})}break;case 3:o(i.styleSheet);break}})}try{e(l.doc.styleSheets,o)}catch(h){}if(g.length>0){l.classes=g}return g},run:function(j,i,h){var g=this,k;if(g.doc&&typeof(j)==="string"){j=g.get(j)}if(!j){return false}h=h||this;if(!j.nodeType&&(j.length||j.length===0)){k=[];e(j,function(l,f){if(l){if(typeof(l)=="string"){l=g.doc.getElementById(l)}k.push(i.call(h,l,f))}});return k}return i.call(h,j)},getAttribs:function(g){var f;g=this.get(g);if(!g){return[]}if(a){f=[];if(g.nodeName=="OBJECT"){return g.attributes}g.cloneNode(false).outerHTML.replace(/([a-z0-9\:\-_]+)=/gi,function(i,h){f.push({specified:1,nodeName:h})});return f}return g.attributes},destroy:function(g){var f=this;if(f.events){f.events.destroy()}f.win=f.doc=f.root=f.events=null;if(!g){c.removeUnload(f.destroy)}},createRng:function(){var f=this.doc;return f.createRange?f.createRange():new c.dom.Range(this)},split:function(k,j,n){var o=this,f=o.createRng(),l,i,m;function g(q,p){q=q[p];if(q&&q[p]&&q[p].nodeType==1&&h(q[p])){o.remove(q[p])}}function h(p){p=o.getOuterHTML(p);p=p.replace(/<(img|hr|table)/gi,"-");p=p.replace(/<[^>]+>/g,"");return p.replace(/[ \t\r\n]+| | /g,"")==""}if(k&&j){f.setStartBefore(k);f.setEndBefore(j);l=f.extractContents();f=o.createRng();f.setStartAfter(j);f.setEndAfter(k);i=f.extractContents();m=k.parentNode;g(l,"lastChild");if(!h(l)){m.insertBefore(l,k)}if(n){m.replaceChild(n,j)}else{m.insertBefore(j,k)}g(i,"firstChild");if(!h(i)){m.insertBefore(i,k)}o.remove(k);return n||j}},bind:function(j,f,i,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.add(j,f,i,h||this)},unbind:function(i,f,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.remove(i,f,h)},_isRes:function(f){return/^(top|left|bottom|right|width|height)/i.test(f)||/;\s*(top|left|bottom|right|width|height)/i.test(f)}});c.DOM=new c.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(f){var h=0,c=1,e=2,d=tinymce.extend;function g(m,k){var j,l;if(m.parentNode!=k){return -1}for(l=k.firstChild,j=0;l!=m;l=l.nextSibling){j++}return j}function b(k){var j=0;while(k.previousSibling){j++;k=k.previousSibling}return j}function i(j,k){var l;if(j.nodeType==3){return j}if(k<0){return j}l=j.firstChild;while(l!=null&&k>0){--k;l=l.nextSibling}if(l!=null){return l}return j}function a(k){var j=k.doc;d(this,{dom:k,startContainer:j,startOffset:0,endContainer:j,endOffset:0,collapsed:true,commonAncestorContainer:j,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3})}d(a.prototype,{setStart:function(k,j){this._setEndPoint(true,k,j)},setEnd:function(k,j){this._setEndPoint(false,k,j)},setStartBefore:function(j){this.setStart(j.parentNode,b(j))},setStartAfter:function(j){this.setStart(j.parentNode,b(j)+1)},setEndBefore:function(j){this.setEnd(j.parentNode,b(j))},setEndAfter:function(j){this.setEnd(j.parentNode,b(j)+1)},collapse:function(k){var j=this;if(k){j.endContainer=j.startContainer;j.endOffset=j.startOffset}else{j.startContainer=j.endContainer;j.startOffset=j.endOffset}j.collapsed=true},selectNode:function(j){this.setStartBefore(j);this.setEndAfter(j)},selectNodeContents:function(j){this.setStart(j,0);this.setEnd(j,j.nodeType===1?j.childNodes.length:j.nodeValue.length)},compareBoundaryPoints:function(m,n){var l=this,p=l.startContainer,o=l.startOffset,k=l.endContainer,j=l.endOffset;if(m===0){return l._compareBoundaryPoints(p,o,p,o)}if(m===1){return l._compareBoundaryPoints(p,o,k,j)}if(m===2){return l._compareBoundaryPoints(k,j,k,j)}if(m===3){return l._compareBoundaryPoints(k,j,p,o)}},deleteContents:function(){this._traverse(e)},extractContents:function(){return this._traverse(h)},cloneContents:function(){return this._traverse(c)},insertNode:function(m){var j=this,l,k;if(m.nodeType===3||m.nodeType===4){l=j.startContainer.splitText(j.startOffset);j.startContainer.parentNode.insertBefore(m,l)}else{if(j.startContainer.childNodes.length>0){k=j.startContainer.childNodes[j.startOffset]}j.startContainer.insertBefore(m,k)}},surroundContents:function(l){var j=this,k=j.extractContents();j.insertNode(l);l.appendChild(k);j.selectNode(l)},cloneRange:function(){var j=this;return d(new a(j.dom),{startContainer:j.startContainer,startOffset:j.startOffset,endContainer:j.endContainer,endOffset:j.endOffset,collapsed:j.collapsed,commonAncestorContainer:j.commonAncestorContainer})},_isCollapsed:function(){return(this.startContainer==this.endContainer&&this.startOffset==this.endOffset)},_compareBoundaryPoints:function(m,p,k,o){var q,l,j,r,t,s;if(m==k){if(p==o){return 0}else{if(p0){l.collapse(k)}}l.collapsed=l._isCollapsed();l.commonAncestorContainer=l.dom.findCommonAncestor(l.startContainer,l.endContainer)},_traverse:function(r){var s=this,q,m=0,v=0,k,o,l,n,j,u;if(s.startContainer==s.endContainer){return s._traverseSameContainer(r)}for(q=s.endContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.startContainer){return s._traverseCommonStartContainer(q,r)}++m}for(q=s.startContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.endContainer){return s._traverseCommonEndContainer(q,r)}++v}o=v-m;l=s.startContainer;while(o>0){l=l.parentNode;o--}n=s.endContainer;while(o<0){n=n.parentNode;o++}for(j=l.parentNode,u=n.parentNode;j!=u;j=j.parentNode,u=u.parentNode){l=j;n=u}return s._traverseCommonAncestors(l,n,r)},_traverseSameContainer:function(o){var r=this,q,u,j,k,l,p,m;if(o!=e){q=r.dom.doc.createDocumentFragment()}if(r.startOffset==r.endOffset){return q}if(r.startContainer.nodeType==3){u=r.startContainer.nodeValue;j=u.substring(r.startOffset,r.endOffset);if(o!=c){r.startContainer.deleteData(r.startOffset,r.endOffset-r.startOffset);r.collapse(true)}if(o==e){return null}q.appendChild(r.dom.doc.createTextNode(j));return q}k=i(r.startContainer,r.startOffset);l=r.endOffset-r.startOffset;while(l>0){p=k.nextSibling;m=r._traverseFullySelected(k,o);if(q){q.appendChild(m)}--l;k=p}if(o!=c){r.collapse(true)}return q},_traverseCommonStartContainer:function(j,p){var s=this,r,k,l,m,q,o;if(p!=e){r=s.dom.doc.createDocumentFragment()}k=s._traverseRightBoundary(j,p);if(r){r.appendChild(k)}l=g(j,s.startContainer);m=l-s.startOffset;if(m<=0){if(p!=c){s.setEndBefore(j);s.collapse(false)}return r}k=j.previousSibling;while(m>0){q=k.previousSibling;o=s._traverseFullySelected(k,p);if(r){r.insertBefore(o,r.firstChild)}--m;k=q}if(p!=c){s.setEndBefore(j);s.collapse(false)}return r},_traverseCommonEndContainer:function(m,p){var s=this,r,o,j,k,q,l;if(p!=e){r=s.dom.doc.createDocumentFragment()}j=s._traverseLeftBoundary(m,p);if(r){r.appendChild(j)}o=g(m,s.endContainer);++o;k=s.endOffset-o;j=m.nextSibling;while(k>0){q=j.nextSibling;l=s._traverseFullySelected(j,p);if(r){r.appendChild(l)}--k;j=q}if(p!=c){s.setStartAfter(m);s.collapse(true)}return r},_traverseCommonAncestors:function(p,j,s){var w=this,l,v,o,q,r,k,u,m;if(s!=e){v=w.dom.doc.createDocumentFragment()}l=w._traverseLeftBoundary(p,s);if(v){v.appendChild(l)}o=p.parentNode;q=g(p,o);r=g(j,o);++q;k=r-q;u=p.nextSibling;while(k>0){m=u.nextSibling;l=w._traverseFullySelected(u,s);if(v){v.appendChild(l)}u=m;--k}l=w._traverseRightBoundary(j,s);if(v){v.appendChild(l)}if(s!=c){w.setStartAfter(p);w.collapse(true)}return v},_traverseRightBoundary:function(p,q){var s=this,l=i(s.endContainer,s.endOffset-1),r,o,n,j,k;var m=l!=s.endContainer;if(l==p){return s._traverseNode(l,m,false,q)}r=l.parentNode;o=s._traverseNode(r,false,false,q);while(r!=null){while(l!=null){n=l.previousSibling;j=s._traverseNode(l,m,false,q);if(q!=e){o.insertBefore(j,o.firstChild)}m=true;l=n}if(r==p){return o}l=r.previousSibling;r=r.parentNode;k=s._traverseNode(r,false,false,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseLeftBoundary:function(p,q){var s=this,m=i(s.startContainer,s.startOffset);var n=m!=s.startContainer,r,o,l,j,k;if(m==p){return s._traverseNode(m,n,true,q)}r=m.parentNode;o=s._traverseNode(r,false,true,q);while(r!=null){while(m!=null){l=m.nextSibling;j=s._traverseNode(m,n,true,q);if(q!=e){o.appendChild(j)}n=true;m=l}if(r==p){return o}m=r.nextSibling;r=r.parentNode;k=s._traverseNode(r,false,true,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseNode:function(j,o,r,s){var u=this,m,l,p,k,q;if(o){return u._traverseFullySelected(j,s)}if(j.nodeType==3){m=j.nodeValue;if(r){k=u.startOffset;l=m.substring(k);p=m.substring(0,k)}else{k=u.endOffset;l=m.substring(0,k);p=m.substring(k)}if(s!=c){j.nodeValue=p}if(s==e){return null}q=j.cloneNode(false);q.nodeValue=l;return q}if(s==e){return null}return j.cloneNode(false)},_traverseFullySelected:function(l,k){var j=this;if(k!=e){return k==c?l.cloneNode(true):l}l.parentNode.removeChild(l);return null}});f.Range=a})(tinymce.dom);(function(){function a(e){var d=this,h="\uFEFF",b,g;function c(j,i){if(j&&i){if(j.item&&i.item&&j.item(0)===i.item(0)){return 1}if(j.isEqual&&i.isEqual&&i.isEqual(j)){return 1}}return 0}function f(){var m=e.dom,j=e.getRng(),s=m.createRng(),p,k,n,q,o,l;function i(v){var t=v.parentNode.childNodes,u;for(u=t.length-1;u>=0;u--){if(t[u]==v){return u}}return -1}function r(v){var t=j.duplicate(),B,y,u,w,x=0,z=0,A,C;t.collapse(v);B=t.parentElement();t.pasteHTML(h);u=B.childNodes;for(y=0;y0&&(w.nodeType!==3||u[y-1].nodeType!==3)){z++}if(w.nodeType===3){A=w.nodeValue.indexOf(h);if(A!==-1){x+=A;break}x+=w.nodeValue.length}else{x=0}}t.moveStart("character",-1);t.text="";return{index:z,offset:x,parent:B}}n=j.item?j.item(0):j.parentElement();if(n.ownerDocument!=m.doc){return s}if(j.item||!n.hasChildNodes()){s.setStart(n.parentNode,i(n));s.setEnd(s.startContainer,s.startOffset+1);return s}l=e.isCollapsed();p=r(true);k=r(false);p.parent.normalize();k.parent.normalize();q=p.parent.childNodes[Math.min(p.index,p.parent.childNodes.length-1)];if(q.nodeType!=3){s.setStart(p.parent,p.index)}else{s.setStart(p.parent.childNodes[p.index],p.offset)}o=k.parent.childNodes[Math.min(k.index,k.parent.childNodes.length-1)];if(o.nodeType!=3){if(!l){k.index++}s.setEnd(k.parent,k.index)}else{s.setEnd(k.parent.childNodes[k.index],k.offset)}if(!l){q=s.startContainer;if(q.nodeType==1){s.setStart(q,Math.min(s.startOffset,q.childNodes.length))}o=s.endContainer;if(o.nodeType==1){s.setEnd(o,Math.min(s.endOffset,o.childNodes.length))}}d.addRange(s);return s}this.addRange=function(j){var o,m=e.dom.doc.body,p,k,q,l,n,i;q=j.startContainer;l=j.startOffset;n=j.endContainer;i=j.endOffset;o=m.createTextRange();q=q.nodeType==1?q.childNodes[Math.min(l,q.childNodes.length-1)]:q;n=n.nodeType==1?n.childNodes[Math.min(l==i?i:i-1,n.childNodes.length-1)]:n;if(q==n&&q.nodeType==1){if(/^(IMG|TABLE)$/.test(q.nodeName)&&l!=i){o=m.createControlRange();o.addElement(q)}else{o=m.createTextRange();if(!q.hasChildNodes()&&q.canHaveHTML){q.innerHTML=h}o.moveToElementText(q);if(q.innerHTML==h){o.collapse(true);q.removeChild(q.firstChild)}}if(l==i){o.collapse(i<=j.endContainer.childNodes.length-1)}o.select();return}function r(t,v){var u,s,w;if(t.nodeType!=3){return -1}u=t.nodeValue;s=m.createTextRange();t.nodeValue=u.substring(0,v)+h+u.substring(v);s.moveToElementText(t.parentNode);s.findText(h);w=Math.abs(s.moveStart("character",-1048575));t.nodeValue=u;return w}if(j.collapsed){pos=r(q,l);o=m.createTextRange();o.move("character",pos);o.select();return}else{if(q==n&&q.nodeType==3){p=r(q,l);o.move("character",p);o.moveEnd("character",i-l);o.select();return}p=r(q,l);k=r(n,i);o=m.createTextRange();if(p==-1){o.moveToElementText(q);p=0}else{o.move("character",p)}tmpRng=m.createTextRange();if(k==-1){tmpRng.moveToElementText(n)}else{tmpRng.move("character",k)}o.setEndPoint("EndToEnd",tmpRng);o.select();return}};this.getRangeAt=function(){if(!b||!c(g,e.getRng())){b=f();g=e.getRng()}return b};this.destroy=function(){g=b=null}}tinymce.dom.TridentSelection=a})();(function(){var p=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,i=0,d=Object.prototype.toString,n=false;var b=function(D,t,A,v){A=A||[];var e=t=t||document;if(t.nodeType!==1&&t.nodeType!==9){return[]}if(!D||typeof D!=="string"){return A}var B=[],C,y,G,F,z,s,r=true,w=o(t);p.lastIndex=0;while((C=p.exec(D))!==null){B.push(C[1]);if(C[2]){s=RegExp.rightContext;break}}if(B.length>1&&j.exec(D)){if(B.length===2&&f.relative[B[0]]){y=g(B[0]+B[1],t)}else{y=f.relative[B[0]]?[t]:b(B.shift(),t);while(B.length){D=B.shift();if(f.relative[D]){D+=B.shift()}y=g(D,y)}}}else{if(!v&&B.length>1&&t.nodeType===9&&!w&&f.match.ID.test(B[0])&&!f.match.ID.test(B[B.length-1])){var H=b.find(B.shift(),t,w);t=H.expr?b.filter(H.expr,H.set)[0]:H.set[0]}if(t){var H=v?{expr:B.pop(),set:a(v)}:b.find(B.pop(),B.length===1&&(B[0]==="~"||B[0]==="+")&&t.parentNode?t.parentNode:t,w);y=H.expr?b.filter(H.expr,H.set):H.set;if(B.length>0){G=a(y)}else{r=false}while(B.length){var u=B.pop(),x=u;if(!f.relative[u]){u=""}else{x=B.pop()}if(x==null){x=t}f.relative[u](G,x,w)}}else{G=B=[]}}if(!G){G=y}if(!G){throw"Syntax error, unrecognized expression: "+(u||D)}if(d.call(G)==="[object Array]"){if(!r){A.push.apply(A,G)}else{if(t&&t.nodeType===1){for(var E=0;G[E]!=null;E++){if(G[E]&&(G[E]===true||G[E].nodeType===1&&h(t,G[E]))){A.push(y[E])}}}else{for(var E=0;G[E]!=null;E++){if(G[E]&&G[E].nodeType===1){A.push(y[E])}}}}}else{a(G,A)}if(s){b(s,e,A,v);b.uniqueSort(A)}return A};b.uniqueSort=function(r){if(c){n=false;r.sort(c);if(n){for(var e=1;e":function(w,r,x){var u=typeof r==="string";if(u&&!/\W/.test(r)){r=x?r:r.toUpperCase();for(var s=0,e=w.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,w){var t=u[1].replace(/\\/g,"");if(!w&&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(w,s,t,x){var r=s[1],u=f.filters[r];if(u){return u(w,t,s,x)}else{if(r==="contains"){return(w.textContent||w.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),w=e+"",u=t[2],r=t[4];return e==null?u==="!=":u==="="?w===r:u==="*="?w.indexOf(r)>=0:u==="~="?(" "+w+" ").indexOf(r)>=0:!r?w&&e!==false:u==="!="?w!=r:u==="^="?w.indexOf(r)===0:u==="$="?w.substr(w.length-r.length)===r:u==="|="?w===r||w.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,w){if(typeof v.getElementById!=="undefined"&&!w){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(w,v,t,u){v=v||document;if(!u&&v.nodeType===9&&!o(v)){try{return a(v.querySelectorAll(w),t)}catch(x){}}return e(w,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,w,v,A,x,z){var y=r=="previousSibling"&&!z;for(var t=0,s=A.length;t0){u=e;break}}}e=e[r]}A[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,x){var t=[],u="",v,s=x.nodeType?[x]:x;while((v=f.match.PSEUDO.exec(e))){u+=v[0];e=e.replace(f.match.PSEUDO,"")}e=f.relative[e]?e+"*":e;for(var w=0,r=s.length;w=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){var b=a.each;a.create("tinymce.dom.Element",{Element:function(g,e){var c=this,f,d;e=e||{};c.id=g;c.dom=f=e.dom||a.DOM;c.settings=e;if(!a.isIE){d=c.dom.get(c.id)}b(["getPos","getRect","getParent","add","setStyle","getStyle","setStyles","setAttrib","setAttribs","getAttrib","addClass","removeClass","hasClass","getOuterHTML","setOuterHTML","remove","show","hide","isHidden","setHTML","get"],function(h){c[h]=function(){var j=[g],k;for(k=0;k_';j.deleteContents();j.insertNode(f.getRng().createContextualFragment(i));l=f.dom.get("__caret");j=k.createRange();j.setStartBefore(l);j.setEndAfter(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(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(1);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.firstChild}return h}else{h=g.startContainer;if(h.nodeName=="BODY"){return h.firstChild}return f.dom.getParent(h,"*")}},getEnd:function(){var f=this,g=f.getRng(),h;if(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(0);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.lastChild}return h}else{h=g.endContainer;if(h.nodeName=="BODY"){return h.lastChild}return f.dom.getParent(h,"*")}},getBookmark:function(x){var j=this,m=j.getRng(),f,n,l,u=j.dom.getViewPort(j.win),v,p,z,o,w=-16777215,k,h=j.dom.getRoot(),g=0,i=0,y;n=u.x;l=u.y;if(x){return{rng:m,scrollX:n,scrollY:l}}if(a){if(m.item){v=m.item(0);d(j.dom.select(v.nodeName),function(s,r){if(v==s){p=r;return false}});return{tag:v.nodeName,index:p,scrollX:n,scrollY:l}}f=j.dom.doc.body.createTextRange();f.moveToElementText(h);f.collapse(true);z=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(true);p=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(false);o=Math.abs(f.move("character",w))-p;return{start:p-z,length:o,scrollX:n,scrollY:l}}v=j.getNode();k=j.getSel();if(!k){return null}if(v&&v.nodeName=="IMG"){return{scrollX:n,scrollY:l}}function q(A,D,t){var s=j.dom.doc.createTreeWalker(A,NodeFilter.SHOW_TEXT,null,false),E,B=0,C={};while((E=s.nextNode())!=null){if(E==D){C.start=B}if(E==t){C.end=B;return C}B+=e(E.nodeValue||"").length}return null}if(k.anchorNode==k.focusNode&&k.anchorOffset==k.focusOffset){v=q(h,k.anchorNode,k.focusNode);if(!v){return{scrollX:n,scrollY:l}}e(k.anchorNode.nodeValue||"").replace(/^\s+/,function(r){g=r.length});return{start:Math.max(v.start+k.anchorOffset-g,0),end:Math.max(v.end+k.focusOffset-g,0),scrollX:n,scrollY:l,beg:k.anchorOffset-g==0}}else{v=q(h,m.startContainer,m.endContainer);if(!v){return{scrollX:n,scrollY:l}}return{start:Math.max(v.start+m.startOffset-g,0),end:Math.max(v.end+m.endOffset-i,0),scrollX:n,scrollY:l,beg:m.startOffset-g==0}}},moveToBookmark:function(n){var o=this,g=o.getRng(),p=o.getSel(),j=o.dom.getRoot(),m,h,k;function i(q,t,D){var B=o.dom.doc.createTreeWalker(q,NodeFilter.SHOW_TEXT,null,false),x,s=0,A={},u,C,z,y;while((x=B.nextNode())!=null){z=y=0;k=x.nodeValue||"";h=e(k).length;s+=h;if(s>=t&&!A.startNode){u=t-(s-h);if(n.beg&&u>=h){continue}A.startNode=x;A.startOffset=u+y}if(s>=D){A.endNode=x;A.endOffset=D-(s-h)+y;return A}}return null}if(!n){return false}o.win.scrollTo(n.scrollX,n.scrollY);if(a){if(g=n.rng){try{g.select()}catch(l){}return true}o.win.focus();if(n.tag){g=j.createControlRange();d(o.dom.select(n.tag),function(r,q){if(q==n.index){g.addElement(r)}})}else{try{if(n.start<0){return true}g=p.createRange();g.moveToElementText(j);g.collapse(true);g.moveStart("character",n.start);g.moveEnd("character",n.length)}catch(f){return true}}try{g.select()}catch(l){}return true}if(!p){return false}if(n.rng){p.removeAllRanges();p.addRange(n.rng)}else{if(b(n.start)&&b(n.end)){try{m=i(j,n.start,n.end);if(m){g=o.dom.doc.createRange();g.setStart(m.startNode,m.startOffset);g.setEnd(m.endNode,m.endOffset);p.removeAllRanges();p.addRange(g)}if(!c.isOpera){o.win.focus()}}catch(l){}}}},select:function(g,l){var p=this,f=p.getRng(),q=p.getSel(),o,m,k,j=p.win.document;function h(u,t){var s,r;if(u){s=j.createTreeWalker(u,NodeFilter.SHOW_TEXT,null,false);while(u=s.nextNode()){r=u;if(c.trim(u.nodeValue).length!=0){if(t){return u}else{r=u}}}}return r}if(a){try{o=j.body;if(/^(IMG|TABLE)$/.test(g.nodeName)){f=o.createControlRange();f.addElement(g)}else{f=o.createTextRange();f.moveToElementText(g)}f.select()}catch(i){}}else{if(l){m=h(g,1)||p.dom.select("br:first",g)[0];k=h(g,0)||p.dom.select("br:last",g)[0];if(m&&k){f=j.createRange();if(m.nodeName=="BR"){f.setStartBefore(m)}else{f.setStart(m,0)}if(k.nodeName=="BR"){f.setEndBefore(k)}else{f.setEnd(k,k.nodeValue.length)}}else{f.selectNode(g)}}else{f.selectNode(g)}p.setRng(f)}return g},isCollapsed:function(){var f=this,h=f.getRng(),g=f.getSel();if(!h||h.item){return false}return !g||h.boundingWidth==0||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=a?g.win.document.body.createTextRange():g.win.document.createRange()}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 f=this,h=f.getRng(),g=f.getSel(),i;if(!a){if(!h){return f.dom.getRoot()}i=h.commonAncestorContainer;if(!h.collapsed){if(c.isWebKit&&g.anchorNode&&g.anchorNode.nodeType==1){return g.anchorNode.childNodes[g.anchorOffset]}if(h.startContainer==h.endContainer){if(h.startOffset-h.endOffset<2){if(h.startContainer.hasChildNodes()){i=h.startContainer.childNodes[h.startOffset]}}}}return f.dom.getParent(i,"*")}return h.item?h.item(0):h.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(""+b+">")}if(this.settings.indentation>0){this.writeRaw("\n")}}},writeFullEndElement:function(){if(this.tags.length>0){this._writeAttributesEnd();this.writeRaw(""+this.tags.pop()+">");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",bool_attrs:/(checked|disabled|readonly|selected|nowrap)/,valid_elements:"*[*]",extended_valid_elements:0,valid_child_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,font_size_style_values: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;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""+o+">"}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,y,w=["ol","ul"],u,t,q,k=/^(OL|UL)$/,z;function m(r,x){var o=x.split(","),p;while((r=r.previousSibling)!=null){for(p=0;p1){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]*>)(.*?)(<\/script>)/g},{pattern:/(]*>)(.*?)(<\/noscript>)/g},{pattern:/("});j=j.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(h,l,k){return""})}j=j.replace(//g,"");j=j.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi,function(h,l){function k(o,n,q){var p=q;if(h.indexOf("mce_"+n)!=-1){return o}if(n=="style"){if(g._isRes(q)){return o}if(i.hex_colors){p=p.replace(/rgb\([^\)]+\)/g,function(m){return g.toHex(m)})}if(i.url_converter){p=p.replace(/url\([\'\"]?([^\)\'\"]+)\)/g,function(m,r){return"url("+g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(r),n,l))+")"})}}else{if(n!="coords"&&n!="shape"){if(i.url_converter){p=g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(q),n,l))}}}return" "+n+'="'+q+'" mce_'+n+'="'+p+'"'}h=h.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi,k);h=h.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi,k);return h.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi,k)})}return j},getOuterHTML:function(f){var g;f=this.get(f);if(!f){return null}if(f.outerHTML!==undefined){return f.outerHTML}g=(f.ownerDocument||this.doc).createElement("body");g.appendChild(f.cloneNode(true));return g.innerHTML},setOuterHTML:function(i,g,j){var f=this;return this.run(i,function(h){var l,k;h=f.get(h);j=j||h.ownerDocument||f.doc;if(a&&h.nodeType==1){h.outerHTML=g}else{k=j.createElement("body");k.innerHTML=g;l=k.lastChild;while(l){f.insertAfter(l.cloneNode(true),h);l=l.previousSibling}f.remove(h)}})},decode:function(g){var h,i,f;if(/&[^;]+;/.test(g)){h=this.doc.createElement("div");h.innerHTML=g;i=h.firstChild;f="";if(i){do{f+=i.nodeValue}while(i.nextSibling)}return f||g}return g},encode:function(f){return f?(""+f).replace(/[<>&\"]/g,function(h,g){switch(h){case"&":return"&";case'"':return""";case"<":return"<";case">":return">"}return h}):f},insertAfter:function(h,g){var f=this;g=f.get(g);return this.run(h,function(k){var j,i;j=g.parentNode;i=g.nextSibling;if(i){j.insertBefore(k,i)}else{j.appendChild(k)}return k})},isBlock:function(f){if(f.nodeType&&f.nodeType!==1){return false}f=f.nodeName||f;return/^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(f)},replace:function(i,h,f){var g=this;if(b(h,"array")){i=i.cloneNode(true)}return g.run(h,function(j){if(f){e(j.childNodes,function(k){i.appendChild(k.cloneNode(true))})}if(g.fixPsuedoLeaks&&j.nodeType===1){j.parentNode.insertBefore(i,j);g.remove(j);return i}return j.parentNode.replaceChild(i,j)})},findCommonAncestor:function(h,f){var i=h,g;while(i){g=f;while(g&&i!=g){g=g.parentNode}if(i==g){break}i=i.parentNode}if(!i&&h.ownerDocument){return h.ownerDocument.documentElement}return i},toHex:function(f){var h=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(f);function g(i){i=parseInt(i).toString(16);return i.length>1?i:"0"+i}if(h){f="#"+g(h[1])+g(h[2])+g(h[3]);return f}return f},getClasses:function(){var l=this,g=[],k,m={},n=l.settings.class_filter,j;if(l.classes){return l.classes}function o(f){e(f.imports,function(i){o(i)});e(f.cssRules||f.rules,function(i){switch(i.type||1){case 1:if(i.selectorText){e(i.selectorText.split(","),function(p){p=p.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(p)||!/\.[\w\-]+$/.test(p)){return}j=p;p=p.replace(/.*\.([a-z0-9_\-]+).*/i,"$1");if(n&&!(p=n(p,j))){return}if(!m[p]){g.push({"class":p});m[p]=1}})}break;case 3:o(i.styleSheet);break}})}try{e(l.doc.styleSheets,o)}catch(h){}if(g.length>0){l.classes=g}return g},run:function(j,i,h){var g=this,k;if(g.doc&&typeof(j)==="string"){j=g.get(j)}if(!j){return false}h=h||this;if(!j.nodeType&&(j.length||j.length===0)){k=[];e(j,function(l,f){if(l){if(typeof(l)=="string"){l=g.doc.getElementById(l)}k.push(i.call(h,l,f))}});return k}return i.call(h,j)},getAttribs:function(g){var f;g=this.get(g);if(!g){return[]}if(a){f=[];if(g.nodeName=="OBJECT"){return g.attributes}g.cloneNode(false).outerHTML.replace(/([a-z0-9\:\-_]+)=/gi,function(i,h){f.push({specified:1,nodeName:h})});return f}return g.attributes},destroy:function(g){var f=this;if(f.events){f.events.destroy()}f.win=f.doc=f.root=f.events=null;if(!g){c.removeUnload(f.destroy)}},createRng:function(){var f=this.doc;return f.createRange?f.createRange():new c.dom.Range(this)},split:function(k,j,n){var o=this,f=o.createRng(),l,i,m;function g(q,p){q=q[p];if(q&&q[p]&&q[p].nodeType==1&&h(q[p])){o.remove(q[p])}}function h(p){p=o.getOuterHTML(p);p=p.replace(/<(img|hr|table)/gi,"-");p=p.replace(/<[^>]+>/g,"");return p.replace(/[ \t\r\n]+| | /g,"")==""}if(k&&j){f.setStartBefore(k);f.setEndBefore(j);l=f.extractContents();f=o.createRng();f.setStartAfter(j);f.setEndAfter(k);i=f.extractContents();m=k.parentNode;g(l,"lastChild");if(!h(l)){m.insertBefore(l,k)}if(n){m.replaceChild(n,j)}else{m.insertBefore(j,k)}g(i,"firstChild");if(!h(i)){m.insertBefore(i,k)}o.remove(k);return n||j}},bind:function(j,f,i,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.add(j,f,i,h||this)},unbind:function(i,f,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.remove(i,f,h)},_isRes:function(f){return/^(top|left|bottom|right|width|height)/i.test(f)||/;\s*(top|left|bottom|right|width|height)/i.test(f)}});c.DOM=new c.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(f){var h=0,c=1,e=2,d=tinymce.extend;function g(m,k){var j,l;if(m.parentNode!=k){return -1}for(l=k.firstChild,j=0;l!=m;l=l.nextSibling){j++}return j}function b(k){var j=0;while(k.previousSibling){j++;k=k.previousSibling}return j}function i(j,k){var l;if(j.nodeType==3){return j}if(k<0){return j}l=j.firstChild;while(l!=null&&k>0){--k;l=l.nextSibling}if(l!=null){return l}return j}function a(k){var j=k.doc;d(this,{dom:k,startContainer:j,startOffset:0,endContainer:j,endOffset:0,collapsed:true,commonAncestorContainer:j,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3})}d(a.prototype,{setStart:function(k,j){this._setEndPoint(true,k,j)},setEnd:function(k,j){this._setEndPoint(false,k,j)},setStartBefore:function(j){this.setStart(j.parentNode,b(j))},setStartAfter:function(j){this.setStart(j.parentNode,b(j)+1)},setEndBefore:function(j){this.setEnd(j.parentNode,b(j))},setEndAfter:function(j){this.setEnd(j.parentNode,b(j)+1)},collapse:function(k){var j=this;if(k){j.endContainer=j.startContainer;j.endOffset=j.startOffset}else{j.startContainer=j.endContainer;j.startOffset=j.endOffset}j.collapsed=true},selectNode:function(j){this.setStartBefore(j);this.setEndAfter(j)},selectNodeContents:function(j){this.setStart(j,0);this.setEnd(j,j.nodeType===1?j.childNodes.length:j.nodeValue.length)},compareBoundaryPoints:function(m,n){var l=this,p=l.startContainer,o=l.startOffset,k=l.endContainer,j=l.endOffset;if(m===0){return l._compareBoundaryPoints(p,o,p,o)}if(m===1){return l._compareBoundaryPoints(p,o,k,j)}if(m===2){return l._compareBoundaryPoints(k,j,k,j)}if(m===3){return l._compareBoundaryPoints(k,j,p,o)}},deleteContents:function(){this._traverse(e)},extractContents:function(){return this._traverse(h)},cloneContents:function(){return this._traverse(c)},insertNode:function(m){var j=this,l,k;if(m.nodeType===3||m.nodeType===4){l=j.startContainer.splitText(j.startOffset);j.startContainer.parentNode.insertBefore(m,l)}else{if(j.startContainer.childNodes.length>0){k=j.startContainer.childNodes[j.startOffset]}j.startContainer.insertBefore(m,k)}},surroundContents:function(l){var j=this,k=j.extractContents();j.insertNode(l);l.appendChild(k);j.selectNode(l)},cloneRange:function(){var j=this;return d(new a(j.dom),{startContainer:j.startContainer,startOffset:j.startOffset,endContainer:j.endContainer,endOffset:j.endOffset,collapsed:j.collapsed,commonAncestorContainer:j.commonAncestorContainer})},_isCollapsed:function(){return(this.startContainer==this.endContainer&&this.startOffset==this.endOffset)},_compareBoundaryPoints:function(m,p,k,o){var q,l,j,r,t,s;if(m==k){if(p==o){return 0}else{if(p0){l.collapse(k)}}l.collapsed=l._isCollapsed();l.commonAncestorContainer=l.dom.findCommonAncestor(l.startContainer,l.endContainer)},_traverse:function(r){var s=this,q,m=0,v=0,k,o,l,n,j,u;if(s.startContainer==s.endContainer){return s._traverseSameContainer(r)}for(q=s.endContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.startContainer){return s._traverseCommonStartContainer(q,r)}++m}for(q=s.startContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.endContainer){return s._traverseCommonEndContainer(q,r)}++v}o=v-m;l=s.startContainer;while(o>0){l=l.parentNode;o--}n=s.endContainer;while(o<0){n=n.parentNode;o++}for(j=l.parentNode,u=n.parentNode;j!=u;j=j.parentNode,u=u.parentNode){l=j;n=u}return s._traverseCommonAncestors(l,n,r)},_traverseSameContainer:function(o){var r=this,q,u,j,k,l,p,m;if(o!=e){q=r.dom.doc.createDocumentFragment()}if(r.startOffset==r.endOffset){return q}if(r.startContainer.nodeType==3){u=r.startContainer.nodeValue;j=u.substring(r.startOffset,r.endOffset);if(o!=c){r.startContainer.deleteData(r.startOffset,r.endOffset-r.startOffset);r.collapse(true)}if(o==e){return null}q.appendChild(r.dom.doc.createTextNode(j));return q}k=i(r.startContainer,r.startOffset);l=r.endOffset-r.startOffset;while(l>0){p=k.nextSibling;m=r._traverseFullySelected(k,o);if(q){q.appendChild(m)}--l;k=p}if(o!=c){r.collapse(true)}return q},_traverseCommonStartContainer:function(j,p){var s=this,r,k,l,m,q,o;if(p!=e){r=s.dom.doc.createDocumentFragment()}k=s._traverseRightBoundary(j,p);if(r){r.appendChild(k)}l=g(j,s.startContainer);m=l-s.startOffset;if(m<=0){if(p!=c){s.setEndBefore(j);s.collapse(false)}return r}k=j.previousSibling;while(m>0){q=k.previousSibling;o=s._traverseFullySelected(k,p);if(r){r.insertBefore(o,r.firstChild)}--m;k=q}if(p!=c){s.setEndBefore(j);s.collapse(false)}return r},_traverseCommonEndContainer:function(m,p){var s=this,r,o,j,k,q,l;if(p!=e){r=s.dom.doc.createDocumentFragment()}j=s._traverseLeftBoundary(m,p);if(r){r.appendChild(j)}o=g(m,s.endContainer);++o;k=s.endOffset-o;j=m.nextSibling;while(k>0){q=j.nextSibling;l=s._traverseFullySelected(j,p);if(r){r.appendChild(l)}--k;j=q}if(p!=c){s.setStartAfter(m);s.collapse(true)}return r},_traverseCommonAncestors:function(p,j,s){var w=this,l,v,o,q,r,k,u,m;if(s!=e){v=w.dom.doc.createDocumentFragment()}l=w._traverseLeftBoundary(p,s);if(v){v.appendChild(l)}o=p.parentNode;q=g(p,o);r=g(j,o);++q;k=r-q;u=p.nextSibling;while(k>0){m=u.nextSibling;l=w._traverseFullySelected(u,s);if(v){v.appendChild(l)}u=m;--k}l=w._traverseRightBoundary(j,s);if(v){v.appendChild(l)}if(s!=c){w.setStartAfter(p);w.collapse(true)}return v},_traverseRightBoundary:function(p,q){var s=this,l=i(s.endContainer,s.endOffset-1),r,o,n,j,k;var m=l!=s.endContainer;if(l==p){return s._traverseNode(l,m,false,q)}r=l.parentNode;o=s._traverseNode(r,false,false,q);while(r!=null){while(l!=null){n=l.previousSibling;j=s._traverseNode(l,m,false,q);if(q!=e){o.insertBefore(j,o.firstChild)}m=true;l=n}if(r==p){return o}l=r.previousSibling;r=r.parentNode;k=s._traverseNode(r,false,false,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseLeftBoundary:function(p,q){var s=this,m=i(s.startContainer,s.startOffset);var n=m!=s.startContainer,r,o,l,j,k;if(m==p){return s._traverseNode(m,n,true,q)}r=m.parentNode;o=s._traverseNode(r,false,true,q);while(r!=null){while(m!=null){l=m.nextSibling;j=s._traverseNode(m,n,true,q);if(q!=e){o.appendChild(j)}n=true;m=l}if(r==p){return o}m=r.nextSibling;r=r.parentNode;k=s._traverseNode(r,false,true,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseNode:function(j,o,r,s){var u=this,m,l,p,k,q;if(o){return u._traverseFullySelected(j,s)}if(j.nodeType==3){m=j.nodeValue;if(r){k=u.startOffset;l=m.substring(k);p=m.substring(0,k)}else{k=u.endOffset;l=m.substring(0,k);p=m.substring(k)}if(s!=c){j.nodeValue=p}if(s==e){return null}q=j.cloneNode(false);q.nodeValue=l;return q}if(s==e){return null}return j.cloneNode(false)},_traverseFullySelected:function(l,k){var j=this;if(k!=e){return k==c?l.cloneNode(true):l}l.parentNode.removeChild(l);return null}});f.Range=a})(tinymce.dom);(function(){function a(e){var d=this,h="\uFEFF",b,g;function c(j,i){if(j&&i){if(j.item&&i.item&&j.item(0)===i.item(0)){return 1}if(j.isEqual&&i.isEqual&&i.isEqual(j)){return 1}}return 0}function f(){var m=e.dom,j=e.getRng(),s=m.createRng(),p,k,n,q,o,l;function i(v){var t=v.parentNode.childNodes,u;for(u=t.length-1;u>=0;u--){if(t[u]==v){return u}}return -1}function r(v){var t=j.duplicate(),B,y,u,w,x=0,z=0,A,C;t.collapse(v);B=t.parentElement();t.pasteHTML(h);u=B.childNodes;for(y=0;y0&&(w.nodeType!==3||u[y-1].nodeType!==3)){z++}if(w.nodeType===3){A=w.nodeValue.indexOf(h);if(A!==-1){x+=A;break}x+=w.nodeValue.length}else{x=0}}t.moveStart("character",-1);t.text="";return{index:z,offset:x,parent:B}}n=j.item?j.item(0):j.parentElement();if(n.ownerDocument!=m.doc){return s}if(j.item||!n.hasChildNodes()){s.setStart(n.parentNode,i(n));s.setEnd(s.startContainer,s.startOffset+1);return s}l=e.isCollapsed();p=r(true);k=r(false);p.parent.normalize();k.parent.normalize();q=p.parent.childNodes[Math.min(p.index,p.parent.childNodes.length-1)];if(q.nodeType!=3){s.setStart(p.parent,p.index)}else{s.setStart(p.parent.childNodes[p.index],p.offset)}o=k.parent.childNodes[Math.min(k.index,k.parent.childNodes.length-1)];if(o.nodeType!=3){if(!l){k.index++}s.setEnd(k.parent,k.index)}else{s.setEnd(k.parent.childNodes[k.index],k.offset)}if(!l){q=s.startContainer;if(q.nodeType==1){s.setStart(q,Math.min(s.startOffset,q.childNodes.length))}o=s.endContainer;if(o.nodeType==1){s.setEnd(o,Math.min(s.endOffset,o.childNodes.length))}}d.addRange(s);return s}this.addRange=function(j){var o,m=e.dom.doc.body,p,k,q,l,n,i;q=j.startContainer;l=j.startOffset;n=j.endContainer;i=j.endOffset;o=m.createTextRange();q=q.nodeType==1?q.childNodes[Math.min(l,q.childNodes.length-1)]:q;n=n.nodeType==1?n.childNodes[Math.min(l==i?i:i-1,n.childNodes.length-1)]:n;if(q==n&&q.nodeType==1){if(/^(IMG|TABLE)$/.test(q.nodeName)&&l!=i){o=m.createControlRange();o.addElement(q)}else{o=m.createTextRange();if(!q.hasChildNodes()&&q.canHaveHTML){q.innerHTML=h}o.moveToElementText(q);if(q.innerHTML==h){o.collapse(true);q.removeChild(q.firstChild)}}if(l==i){o.collapse(i<=j.endContainer.childNodes.length-1)}o.select();return}function r(t,v){var u,s,w;if(t.nodeType!=3){return -1}u=t.nodeValue;s=m.createTextRange();t.nodeValue=u.substring(0,v)+h+u.substring(v);s.moveToElementText(t.parentNode);s.findText(h);w=Math.abs(s.moveStart("character",-1048575));t.nodeValue=u;return w}if(j.collapsed){pos=r(q,l);o=m.createTextRange();o.move("character",pos);o.select();return}else{if(q==n&&q.nodeType==3){p=r(q,l);o.move("character",p);o.moveEnd("character",i-l);o.select();return}p=r(q,l);k=r(n,i);o=m.createTextRange();if(p==-1){o.moveToElementText(q);p=0}else{o.move("character",p)}tmpRng=m.createTextRange();if(k==-1){tmpRng.moveToElementText(n)}else{tmpRng.move("character",k)}o.setEndPoint("EndToEnd",tmpRng);o.select();return}};this.getRangeAt=function(){if(!b||!c(g,e.getRng())){b=f();g=e.getRng()}return b};this.destroy=function(){g=b=null}}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){var b=a.each;a.create("tinymce.dom.Element",{Element:function(g,e){var c=this,f,d;e=e||{};c.id=g;c.dom=f=e.dom||a.DOM;c.settings=e;if(!a.isIE){d=c.dom.get(c.id)}b(["getPos","getRect","getParent","add","setStyle","getStyle","setStyles","setAttrib","setAttribs","getAttrib","addClass","removeClass","hasClass","getOuterHTML","setOuterHTML","remove","show","hide","isHidden","setHTML","get"],function(h){c[h]=function(){var j=[g],k;for(k=0;k_';j.deleteContents();j.insertNode(f.getRng().createContextualFragment(i));l=f.dom.get("__caret");j=k.createRange();j.setStartBefore(l);j.setEndAfter(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(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(1);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.firstChild}return h}else{h=g.startContainer;if(h.nodeName=="BODY"){return h.firstChild}return f.dom.getParent(h,"*")}},getEnd:function(){var f=this,g=f.getRng(),h;if(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(0);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.lastChild}return h}else{h=g.endContainer;if(h.nodeName=="BODY"){return h.lastChild}return f.dom.getParent(h,"*")}},getBookmark:function(x){var j=this,m=j.getRng(),f,n,l,u=j.dom.getViewPort(j.win),v,p,z,o,w=-16777215,k,h=j.dom.getRoot(),g=0,i=0,y;n=u.x;l=u.y;if(x){return{rng:m,scrollX:n,scrollY:l}}if(a){if(m.item){v=m.item(0);d(j.dom.select(v.nodeName),function(s,r){if(v==s){p=r;return false}});return{tag:v.nodeName,index:p,scrollX:n,scrollY:l}}f=j.dom.doc.body.createTextRange();f.moveToElementText(h);f.collapse(true);z=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(true);p=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(false);o=Math.abs(f.move("character",w))-p;return{start:p-z,length:o,scrollX:n,scrollY:l}}v=j.getNode();k=j.getSel();if(!k){return null}if(v&&v.nodeName=="IMG"){return{scrollX:n,scrollY:l}}function q(A,D,t){var s=j.dom.doc.createTreeWalker(A,NodeFilter.SHOW_TEXT,null,false),E,B=0,C={};while((E=s.nextNode())!=null){if(E==D){C.start=B}if(E==t){C.end=B;return C}B+=e(E.nodeValue||"").length}return null}if(k.anchorNode==k.focusNode&&k.anchorOffset==k.focusOffset){v=q(h,k.anchorNode,k.focusNode);if(!v){return{scrollX:n,scrollY:l}}e(k.anchorNode.nodeValue||"").replace(/^\s+/,function(r){g=r.length});return{start:Math.max(v.start+k.anchorOffset-g,0),end:Math.max(v.end+k.focusOffset-g,0),scrollX:n,scrollY:l,beg:k.anchorOffset-g==0}}else{v=q(h,m.startContainer,m.endContainer);if(!v){return{scrollX:n,scrollY:l}}return{start:Math.max(v.start+m.startOffset-g,0),end:Math.max(v.end+m.endOffset-i,0),scrollX:n,scrollY:l,beg:m.startOffset-g==0}}},moveToBookmark:function(n){var o=this,g=o.getRng(),p=o.getSel(),j=o.dom.getRoot(),m,h,k;function i(q,t,D){var B=o.dom.doc.createTreeWalker(q,NodeFilter.SHOW_TEXT,null,false),x,s=0,A={},u,C,z,y;while((x=B.nextNode())!=null){z=y=0;k=x.nodeValue||"";h=e(k).length;s+=h;if(s>=t&&!A.startNode){u=t-(s-h);if(n.beg&&u>=h){continue}A.startNode=x;A.startOffset=u+y}if(s>=D){A.endNode=x;A.endOffset=D-(s-h)+y;return A}}return null}if(!n){return false}o.win.scrollTo(n.scrollX,n.scrollY);if(a){if(g=n.rng){try{g.select()}catch(l){}return true}o.win.focus();if(n.tag){g=j.createControlRange();d(o.dom.select(n.tag),function(r,q){if(q==n.index){g.addElement(r)}})}else{try{if(n.start<0){return true}g=p.createRange();g.moveToElementText(j);g.collapse(true);g.moveStart("character",n.start);g.moveEnd("character",n.length)}catch(f){return true}}try{g.select()}catch(l){}return true}if(!p){return false}if(n.rng){p.removeAllRanges();p.addRange(n.rng)}else{if(b(n.start)&&b(n.end)){try{m=i(j,n.start,n.end);if(m){g=o.dom.doc.createRange();g.setStart(m.startNode,m.startOffset);g.setEnd(m.endNode,m.endOffset);p.removeAllRanges();p.addRange(g)}if(!c.isOpera){o.win.focus()}}catch(l){}}}},select:function(g,l){var p=this,f=p.getRng(),q=p.getSel(),o,m,k,j=p.win.document;function h(u,t){var s,r;if(u){s=j.createTreeWalker(u,NodeFilter.SHOW_TEXT,null,false);while(u=s.nextNode()){r=u;if(c.trim(u.nodeValue).length!=0){if(t){return u}else{r=u}}}}return r}if(a){try{o=j.body;if(/^(IMG|TABLE)$/.test(g.nodeName)){f=o.createControlRange();f.addElement(g)}else{f=o.createTextRange();f.moveToElementText(g)}f.select()}catch(i){}}else{if(l){m=h(g,1)||p.dom.select("br:first",g)[0];k=h(g,0)||p.dom.select("br:last",g)[0];if(m&&k){f=j.createRange();if(m.nodeName=="BR"){f.setStartBefore(m)}else{f.setStart(m,0)}if(k.nodeName=="BR"){f.setEndBefore(k)}else{f.setEnd(k,k.nodeValue.length)}}else{f.selectNode(g)}}else{f.selectNode(g)}p.setRng(f)}return g},isCollapsed:function(){var f=this,h=f.getRng(),g=f.getSel();if(!h||h.item){return false}return !g||h.boundingWidth==0||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=a?g.win.document.body.createTextRange():g.win.document.createRange()}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 f=this,h=f.getRng(),g=f.getSel(),i;if(!a){if(!h){return f.dom.getRoot()}i=h.commonAncestorContainer;if(!h.collapsed){if(c.isWebKit&&g.anchorNode&&g.anchorNode.nodeType==1){return g.anchorNode.childNodes[g.anchorOffset]}if(h.startContainer==h.endContainer){if(h.startOffset-h.endOffset<2){if(h.startContainer.hasChildNodes()){i=h.startContainer.childNodes[h.startOffset]}}}}return f.dom.getParent(i,"*")}return h.item?h.item(0):h.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(""+b+">")}if(this.settings.indentation>0){this.writeRaw("\n")}}},writeFullEndElement:function(){if(this.tags.length>0){this._writeAttributesEnd();this.writeRaw(""+this.tags.pop()+">");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",bool_attrs:/(checked|disabled|readonly|selected|nowrap)/,valid_elements:"*[*]",extended_valid_elements:0,valid_child_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,font_size_style_values: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;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""+o+">"}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,y,w=["ol","ul"],u,t,q,k=/^(OL|UL)$/,z;function m(r,x){var o=x.split(","),p;while((r=r.previousSibling)!=null){for(p=0;p1){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]*>)(.*?)(<\/script>)/g},{pattern:/(]*>)(.*?)(<\/noscript>)/g},{pattern:/('; + }); + + // Wrap noscript elements + h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { + return ''; + }); + } + + h = h.replace(//g, ''); + + // Process all tags with src, href or style + h = h.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi, function(a, n) { + function handle(m, b, c) { + var u = c; + + // Tag already got a mce_ version + if (a.indexOf('mce_' + b) != -1) + return m; + + if (b == 'style') { + // No mce_style for elements with these since they might get resized by the user + if (t._isRes(c)) + return m; + + if (s.hex_colors) { + u = u.replace(/rgb\([^\)]+\)/g, function(v) { + return t.toHex(v); + }); + } + + if (s.url_converter) { + u = u.replace(/url\([\'\"]?([^\)\'\"]+)\)/g, function(x, c) { + return 'url(' + t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)) + ')'; + }); + } + } else if (b != 'coords' && b != 'shape') { + if (s.url_converter) + u = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)); + } + + return ' ' + b + '="' + c + '" mce_' + b + '="' + u + '"'; + }; + + a = a.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi, handle); // W3C + a = a.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi, handle); // W3C + + return a.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi, handle); // IE + }); + } + + 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; + + return this.run(e, function(e) { + var n, tp; + + e = t.get(e); + d = d || e.ownerDocument || t.doc; + + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else { + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + } + }); + }, + + decode : function(s) { + var e, n, v; + + // Look for entities to decode + if (/&[^;]+;/.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.nextSibling); + } + + return v || s; + } + + return s; + }, + + encode : function(s) { + return s ? ('' + s).replace(/[<>&\"]/g, function (c, b) { + switch (c) { + case '&': + return '&'; + + case '"': + return '"'; + + case '<': + return '<'; + + case '>': + return '>'; + } + + return c; + }) : s; + }, + + insertAfter : function(n, r) { + var t = this; + + r = t.get(r); + + return this.run(n, function(n) { + var p, ns; + + p = r.parentNode; + ns = r.nextSibling; + + if (ns) + p.insertBefore(n, ns); + else + p.appendChild(n); + + return n; + }); + }, + + isBlock : function(n) { + if (n.nodeType && n.nodeType !== 1) + return false; + + n = n.nodeName || n; + + return /^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.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(o.childNodes, function(c) { + n.appendChild(c.cloneNode(true)); + }); + } + + // Fix IE psuedo leak for elements since replacing elements if fairly common + // Will break parentNode for some unknown reason + if (t.fixPsuedoLeaks && o.nodeType === 1) { + o.parentNode.insertBefore(n, o); + t.remove(o); + return n; + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + 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; + + // 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(/([a-z0-9\:\-_]+)=/gi, function(a, b) { + o.push({specified : 1, nodeName : b}); + }); + + 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); + }, + + 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 sence + // but we don't want that in our code since it serves no purpose + // For example if this is chopped: + // text 1CHOPtext 2 + // would produce: + // text 1CHOPtext 2 + // this function will then trim of empty edges and produce: + // text 1CHOPtext 2 + function trimEdge(n, na) { + n = n[na]; + + if (n && n[na] && n[na].nodeType == 1 && isEmpty(n[na])) + t.remove(n[na]); + }; + + function isEmpty(n) { + n = t.getOuterHTML(n); + n = n.replace(/<(img|hr|table)/gi, '-'); // Keep these convert them to - chars + n = n.replace(/<[^>]+>/g, ''); // Remove all tags + + return n.replace(/[ \t\r\n]+| | /g, '') == ''; + }; + + if (pe && e) { + // Get before chunk + r.setStartBefore(pe); + r.setEndBefore(e); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStartAfter(e); + r.setEndAfter(pe); + aft = r.extractContents(); + + // Insert chunks and remove parent + pa = pe.parentNode; + + // Remove right side edge of the before contents + trimEdge(bef, 'lastChild'); + + if (!isEmpty(bef)) + pa.insertBefore(bef, pe); + + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Remove left site edge of the after contents + trimEdge(aft, 'firstChild'); + + if (!isEmpty(aft)) + pa.insertBefore(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); + }, + + + _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; + } + */ + + }); + + // Setup page DOM + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); +(function(ns) { + // Traverse constants + var EXTRACT = 0, CLONE = 1, DELETE = 2, extend = tinymce.extend; + + function indexOf(child, parent) { + var i, node; + + if (child.parentNode != parent) + return -1; + + for (node = parent.firstChild, i = 0; node != child; node = node.nextSibling) + i++; + + return i; + }; + + function nodeIndex(n) { + var i = 0; + + while (n.previousSibling) { + i++; + n = n.previousSibling; + } + + return i; + }; + + function getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child != null && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child != null) + return child; + + return container; + }; + + // Range constructor + function Range(dom) { + var d = dom.doc; + + extend(this, { + dom : dom, + + // Inital states + startContainer : d, + startOffset : 0, + endContainer : d, + endOffset : 0, + collapsed : true, + commonAncestorContainer : d, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3 + }); + }; + + // Add range methods + extend(Range.prototype, { + setStart : function(n, o) { + this._setEndPoint(true, n, o); + }, + + setEnd : function(n, o) { + this._setEndPoint(false, n, o); + }, + + setStartBefore : function(n) { + this.setStart(n.parentNode, nodeIndex(n)); + }, + + setStartAfter : function(n) { + this.setStart(n.parentNode, nodeIndex(n) + 1); + }, + + setEndBefore : function(n) { + this.setEnd(n.parentNode, nodeIndex(n)); + }, + + setEndAfter : function(n) { + this.setEnd(n.parentNode, nodeIndex(n) + 1); + }, + + collapse : function(ts) { + var t = this; + + if (ts) { + t.endContainer = t.startContainer; + t.endOffset = t.startOffset; + } else { + t.startContainer = t.endContainer; + t.startOffset = t.endOffset; + } + + t.collapsed = true; + }, + + selectNode : function(n) { + this.setStartBefore(n); + this.setEndAfter(n); + }, + + selectNodeContents : function(n) { + this.setStart(n, 0); + this.setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }, + + compareBoundaryPoints : function(h, r) { + var t = this, sc = t.startContainer, so = t.startOffset, ec = t.endContainer, eo = t.endOffset; + + // Check START_TO_START + if (h === 0) + return t._compareBoundaryPoints(sc, so, sc, so); + + // Check START_TO_END + if (h === 1) + return t._compareBoundaryPoints(sc, so, ec, eo); + + // Check END_TO_END + if (h === 2) + return t._compareBoundaryPoints(ec, eo, ec, eo); + + // Check END_TO_START + if (h === 3) + return t._compareBoundaryPoints(ec, eo, sc, so); + }, + + deleteContents : function() { + this._traverse(DELETE); + }, + + extractContents : function() { + return this._traverse(EXTRACT); + }, + + cloneContents : function() { + return this._traverse(CLONE); + }, + + insertNode : function(n) { + var t = this, nn, o; + + // Node is TEXT_NODE or CDATA + if (n.nodeType === 3 || n.nodeType === 4) { + nn = t.startContainer.splitText(t.startOffset); + t.startContainer.parentNode.insertBefore(n, nn); + } else { + // Insert element node + if (t.startContainer.childNodes.length > 0) + o = t.startContainer.childNodes[t.startOffset]; + + t.startContainer.insertBefore(n, o); + } + }, + + surroundContents : function(n) { + var t = this, f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }, + + cloneRange : function() { + var t = this; + + return extend(new Range(t.dom), { + startContainer : t.startContainer, + startOffset : t.startOffset, + endContainer : t.endContainer, + endOffset : t.endOffset, + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }, + +/* + detach : function() { + // Not implemented + }, +*/ + // Internal methods + + _isCollapsed : function() { + return (this.startContainer == this.endContainer && this.startOffset == this.endOffset); + }, + + _compareBoundaryPoints : function (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 + } else if (offsetA < offsetB) { + return -1; // before + } else { + 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 + } else { + 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 + } else { + 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 = this.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; + } + }, + + _setEndPoint : function(st, n, o) { + var t = this, ec, sc; + + if (st) { + t.startContainer = n; + t.startOffset = o; + } else { + t.endContainer = n; + t.endOffset = 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.endContainer; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t.startContainer; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc != ec) { + t.collapse(st); + } else { + // 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 (t._compareBoundaryPoints(t.startContainer, t.startOffset, t.endContainer, t.endOffset) > 0) + t.collapse(st); + } + + t.collapsed = t._isCollapsed(); + t.commonAncestorContainer = t.dom.findCommonAncestor(t.startContainer, t.endContainer); + }, + + // This code is heavily "inspired" by the Apache Xerces implementation. I hope they don't mind. :) + + _traverse : function(how) { + var t = this, c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t.startContainer == t.endContainer) + return t._traverseSameContainer(how); + + for (c = t.endContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.startContainer) + return t._traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t.startContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.endContainer) + return t._traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t.startContainer; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t.endContainer; + 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 t._traverseCommonAncestors(startNode, endNode, how); + }, + + _traverseSameContainer : function(how) { + var t = this, frag, s, sub, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t.startOffset == t.endOffset) + return frag; + + // Text node needs special case handling + if (t.startContainer.nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t.startContainer.nodeValue; + sub = s.substring(t.startOffset, t.endOffset); + + // set the original text node to its new value + if (how != CLONE) { + t.startContainer.deleteData(t.startOffset, t.endOffset - t.startOffset); + + // Nothing is partially selected, so collapse to start point + t.collapse(true); + } + + if (how == DELETE) + return null; + + frag.appendChild(t.dom.doc.createTextNode(sub)); + return frag; + } + + // Copy nodes between the start/end offsets. + n = getSelectedNode(t.startContainer, t.startOffset); + cnt = t.endOffset - t.startOffset; + + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._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; + }, + + _traverseCommonStartContainer : function(endAncestor, how) { + var t = this, frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = indexOf(endAncestor, t.startContainer); + cnt = endIdx - t.startOffset; + + 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 = t._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; + }, + + _traverseCommonEndContainer : function(startAncestor, how) { + var t = this, frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = indexOf(startAncestor, t.endContainer); + ++startIdx; // Because we already traversed it.... + + cnt = t.endOffset - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseCommonAncestors : function(startAncestor, endAncestor, how) { + var t = this, n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = indexOf(startAncestor, commonParent); + endOffset = indexOf(endAncestor, commonParent); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = t._traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseRightBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.endContainer, t.endOffset - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent; + var isFullySelected = next != t.endContainer; + + if (next == root) + return t._traverseNode(next, isFullySelected, false, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, false, how); + + while (parent != null) { + while (next != null) { + prevSibling = next.previousSibling; + clonedChild = t._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 = t._traverseNode(parent, false, false, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseLeftBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.startContainer, t.startOffset); + var isFullySelected = next != t.startContainer, parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return t._traverseNode(next, isFullySelected, true, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, true, how); + + while (parent != null) { + while (next != null) { + nextSibling = next.nextSibling; + clonedChild = t._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 = t._traverseNode(parent, false, true, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseNode : function(n, isFullySelected, isLeft, how) { + var t = this, txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return t._traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t.startOffset; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t.endOffset; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return null; + + newNode = n.cloneNode(false); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return null; + + return n.cloneNode(false); + }, + + _traverseFullySelected : function(n, how) { + var t = this; + + if (how != DELETE) + return how == CLONE ? n.cloneNode(true) : n; + + n.parentNode.removeChild(n); + return null; + } + }); + + ns.Range = Range; +})(tinymce.dom); +(function() { + function Selection(selection) { + var t = this, invisibleChar = '\uFEFF', range, lastIERng; + + 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 1; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return 1; + } + + return 0; + }; + + function getRange() { + var dom = selection.dom, ieRange = selection.getRng(), domRange = dom.createRng(), startPos, endPos, element, sc, ec, collapsed; + + function findIndex(element) { + var nl = element.parentNode.childNodes, i; + + for (i = nl.length - 1; i >= 0; i--) { + if (nl[i] == element) + return i; + } + + return -1; + }; + + function findEndPoint(start) { + var rng = ieRange.duplicate(), parent, i, nl, n, offset = 0, index = 0, pos, tmpRng; + + // Insert marker character + rng.collapse(start); + parent = rng.parentElement(); + rng.pasteHTML(invisibleChar); // Needs to be a pasteHTML instead of .text = since IE has a bug with nodeValue + + // Find marker character + nl = parent.childNodes; + for (i = 0; i < nl.length; i++) { + n = nl[i]; + + // Calculate node index excluding text node fragmentation + if (i > 0 && (n.nodeType !== 3 || nl[i - 1].nodeType !== 3)) + index++; + + // If text node then calculate offset + if (n.nodeType === 3) { + // Look for marker + pos = n.nodeValue.indexOf(invisibleChar); + if (pos !== -1) { + offset += pos; + break; + } + + offset += n.nodeValue.length; + } else + offset = 0; + } + + // Remove marker character + rng.moveStart('character', -1); + rng.text = ''; + + return {index : index, offset : offset, parent : parent}; + }; + + // 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, findIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + // Check collapsed state + collapsed = selection.isCollapsed(); + + // Find start and end pos index and offset + startPos = findEndPoint(true); + endPos = findEndPoint(false); + + // Normalize the elements to avoid fragmented dom + startPos.parent.normalize(); + endPos.parent.normalize(); + + // Set start container and offset + sc = startPos.parent.childNodes[Math.min(startPos.index, startPos.parent.childNodes.length - 1)]; + + if (sc.nodeType != 3) + domRange.setStart(startPos.parent, startPos.index); + else + domRange.setStart(startPos.parent.childNodes[startPos.index], startPos.offset); + + // Set end container and offset + ec = endPos.parent.childNodes[Math.min(endPos.index, endPos.parent.childNodes.length - 1)]; + + if (ec.nodeType != 3) { + if (!collapsed) + endPos.index++; + + domRange.setEnd(endPos.parent, endPos.index); + } else + domRange.setEnd(endPos.parent.childNodes[endPos.index], endPos.offset); + + // If not collapsed then make sure offsets are valid + if (!collapsed) { + sc = domRange.startContainer; + if (sc.nodeType == 1) + domRange.setStart(sc, Math.min(domRange.startOffset, sc.childNodes.length)); + + ec = domRange.endContainer; + if (ec.nodeType == 1) + domRange.setEnd(ec, Math.min(domRange.endOffset, ec.childNodes.length)); + } + + // Restore selection to new range + t.addRange(domRange); + + return domRange; + }; + + this.addRange = function(rng) { + var ieRng, body = selection.dom.doc.body, startPos, endPos, sc, so, ec, eo; + + // Setup some shorter versions + sc = rng.startContainer; + so = rng.startOffset; + ec = rng.endContainer; + eo = rng.endOffset; + ieRng = body.createTextRange(); + + // Find element + sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc; + ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec; + + // 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(); + + return; + } + + function getCharPos(container, offset) { + var nodeVal, rng, pos; + + if (container.nodeType != 3) + return -1; + + nodeVal = container.nodeValue; + rng = body.createTextRange(); + + // Insert marker at offset position + container.nodeValue = nodeVal.substring(0, offset) + invisibleChar + nodeVal.substring(offset); + + // Find char pos of marker and remove it + rng.moveToElementText(container.parentNode); + rng.findText(invisibleChar); + pos = Math.abs(rng.moveStart('character', -0xFFFFF)); + container.nodeValue = nodeVal; + + return pos; + }; + + // Collapsed range + if (rng.collapsed) { + pos = getCharPos(sc, so); + + ieRng = body.createTextRange(); + ieRng.move('character', pos); + ieRng.select(); + + return; + } else { + // If same text container + if (sc == ec && sc.nodeType == 3) { + startPos = getCharPos(sc, so); + + ieRng.move('character', startPos); + ieRng.moveEnd('character', eo - so); + ieRng.select(); + + return; + } + + // Get caret positions + startPos = getCharPos(sc, so); + endPos = getCharPos(ec, eo); + ieRng = body.createTextRange(); + + // Move start of range to start character position or start element + if (startPos == -1) { + ieRng.moveToElementText(sc); + startPos = 0; + } else + ieRng.move('character', startPos); + + // Move end of range to end character position or end element + tmpRng = body.createTextRange(); + + if (endPos == -1) + tmpRng.moveToElementText(ec); + else + tmpRng.move('character', endPos); + + ieRng.setEndPoint('EndToEnd', tmpRng); + ieRng.select(); + + return; + } + }; + + 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; + }; + }; + + // 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 + // 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; + } + } + + }); + + // Shorten name and setup global instance + 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) { + var each = tinymce.each; + + tinymce.create('tinymce.dom.Element', { + Element : function(id, s) { + var t = this, dom, el; + + s = s || {}; + t.id = id; + t.dom = dom = s.dom || tinymce.DOM; + t.settings = s; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = t.dom.get(t.id); + + each([ + 'getPos', + 'getRect', + 'getParent', + 'add', + 'setStyle', + 'getStyle', + 'setStyles', + 'setAttrib', + 'setAttribs', + 'getAttrib', + 'addClass', + 'removeClass', + 'hasClass', + 'getOuterHTML', + 'setOuterHTML', + 'remove', + 'show', + 'hide', + 'isHidden', + 'setHTML', + 'get' + ], 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; + }; + }); + }, + + on : function(n, f, s) { + return tinymce.dom.Event.add(this.id, n, f, s); + }, + + getXY : function() { + return { + x : parseInt(this.getStyle('left')), + y : parseInt(this.getStyle('top')) + }; + }, + + getSize : function() { + var n = this.dom.get(this.id); + + return { + w : parseInt(this.getStyle('width') || n.clientWidth), + h : parseInt(this.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + this.setStyles({left : x, top : y}); + }, + + moveBy : function(x, y) { + var p = this.getXY(); + + this.moveTo(p.x + x, p.y + y); + }, + + resizeTo : function(w, h) { + this.setStyles({width : w, height : h}); + }, + + resizeBy : function(w, h) { + var s = this.getSize(); + + this.resizeTo(s.w + w, s.h + h); + }, + + update : function(k) { + var t = this, b, dom = t.dom; + + if (tinymce.isIE6 && t.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(t.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.setStyle(b, 'left', t.getStyle('left', 1)); + dom.setStyle(b, 'top', t.getStyle('top', 1)); + dom.setStyle(b, 'width', t.getStyle('width', 1)); + dom.setStyle(b, 'height', t.getStyle('height', 1)); + dom.setStyle(b, 'display', t.getStyle('display', 1)); + dom.setStyle(b, '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 + 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.setEndAfter(c); + t.setRng(r); + + // Delete the marker, and hopefully the caret gets placed in the right location + // Removed this since it seems to remove in FF and simply deleting it + // doesn't seem to affect the caret position in any browser + //d.execCommand('Delete', false, null); + + // 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 (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(1); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.firstChild; + + return e; + } else { + e = r.startContainer; + + if (e.nodeName == 'BODY') + return e.firstChild; + + return t.dom.getParent(e, '*'); + } + }, + + getEnd : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild; + + return e; + } else { + e = r.endContainer; + + if (e.nodeName == 'BODY') + return e.lastChild; + + return t.dom.getParent(e, '*'); + } + }, + + getBookmark : function(si) { + var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv; + sx = vp.x; + sy = vp.y; + + // Simple bookmark fast but not as persistent + if (si) + return {rng : r, scrollX : sx, scrollY : sy}; + + // Handle IE + if (isIE) { + // Control selection + if (r.item) { + e = r.item(0); + + each(t.dom.select(e.nodeName), function(n, i) { + if (e == n) { + sp = i; + return false; + } + }); + + return { + tag : e.nodeName, + index : sp, + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + tr = t.dom.doc.body.createTextRange(); + tr.moveToElementText(ro); + tr.collapse(true); + bp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(true); + sp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(false); + le = Math.abs(tr.move('character', c)) - sp; + + return { + start : sp - bp, + length : le, + scrollX : sx, + scrollY : sy + }; + } + + // Handle W3C + e = t.getNode(); + s = t.getSel(); + + if (!s) + return null; + + // Image selection + if (e && e.nodeName == 'IMG') { + return { + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + + function getPos(r, sn, en) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; + + while ((n = w.nextNode()) != null) { + if (n == sn) + d.start = p; + + if (n == en) { + d.end = p; + return d; + } + + p += trimNl(n.nodeValue || '').length; + } + + return null; + }; + + // Caret or selection + if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) { + e = getPos(ro, s.anchorNode, s.focusNode); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + // Count whitespace before + trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + + return { + start : Math.max(e.start + s.anchorOffset - wb, 0), + end : Math.max(e.end + s.focusOffset - wb, 0), + scrollX : sx, + scrollY : sy, + beg : s.anchorOffset - wb == 0 + }; + } else { + e = getPos(ro, r.startContainer, r.endContainer); + + // Count whitespace before start and end container + //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;}); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + return { + start : Math.max(e.start + r.startOffset - wb, 0), + end : Math.max(e.end + r.endOffset - wa, 0), + scrollX : sx, + scrollY : sy, + beg : r.startOffset - wb == 0 + }; + } + }, + + moveToBookmark : function(b) { + var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv; + + function getPos(r, sp, ep) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb; + + while ((n = w.nextNode()) != null) { + wa = wb = 0; + + nv = n.nodeValue || ''; + //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;}); + //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;}); + + nvl = trimNl(nv).length; + p += nvl; + + if (p >= sp && !d.startNode) { + o = sp - (p - nvl); + + // Fix for odd quirk in FF + if (b.beg && o >= nvl) + continue; + + d.startNode = n; + d.startOffset = o + wb; + } + + if (p >= ep) { + d.endNode = n; + d.endOffset = ep - (p - nvl) + wb; + return d; + } + } + + return null; + }; + + if (!b) + return false; + + t.win.scrollTo(b.scrollX, b.scrollY); + + // Handle explorer + if (isIE) { + // Handle simple + if (r = b.rng) { + try { + r.select(); + } catch (ex) { + // Ignore + } + + return true; + } + + t.win.focus(); + + // Handle control bookmark + if (b.tag) { + r = ro.createControlRange(); + + each(t.dom.select(b.tag), function(n, i) { + if (i == b.index) + r.addElement(n); + }); + } else { + // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs + try { + // Incorrect bookmark + if (b.start < 0) + return true; + + r = s.createRange(); + r.moveToElementText(ro); + r.collapse(true); + r.moveStart('character', b.start); + r.moveEnd('character', b.length); + } catch (ex2) { + return true; + } + } + + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + + return true; + } + + // Handle W3C + if (!s) + return false; + + // Handle simple + if (b.rng) { + s.removeAllRanges(); + s.addRange(b.rng); + } else { + if (is(b.start) && is(b.end)) { + try { + sd = getPos(ro, b.start, b.end); + + if (sd) { + r = t.dom.doc.createRange(); + r.setStart(sd.startNode, sd.startOffset); + r.setEnd(sd.endNode, sd.endOffset); + s.removeAllRanges(); + s.addRange(r); + } + + if (!tinymce.isOpera) + t.win.focus(); + } catch (ex) { + // Ignore + } + } + } + }, + + select : function(n, c) { + var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document; + + function find(n, start) { + var walker, o; + + if (n) { + walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + // Find first/last non empty text node + while (n = walker.nextNode()) { + o = n; + + if (tinymce.trim(n.nodeValue).length != 0) { + if (start) + return n; + else + o = n; + } + } + } + + return o; + }; + + if (isIE) { + try { + b = d.body; + + if (/^(IMG|TABLE)$/.test(n.nodeName)) { + r = b.createControlRange(); + r.addElement(n); + } else { + r = b.createTextRange(); + r.moveToElementText(n); + } + + r.select(); + } catch (ex) { + // Throws illigal agrument in IE some times + } + } else { + if (c) { + fn = find(n, 1) || t.dom.select('br:first', n)[0]; + ln = find(n, 0) || t.dom.select('br:last', n)[0]; + + if (fn && ln) { + r = d.createRange(); + + if (fn.nodeName == 'BR') + r.setStartBefore(fn); + else + r.setStart(fn, 0); + + if (ln.nodeName == 'BR') + r.setEndBefore(ln); + else + r.setEnd(ln, ln.nodeValue.length); + } else + r.selectNode(n); + } else + r.selectNode(n); + + t.setRng(r); + } + + return n; + }, + + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + return !s || r.boundingWidth == 0 || 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 = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange(); + + 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, r = t.getRng(), s = t.getSel(), e; + + if (!isIE) { + // Range maybe lost after the editor is made visible again + if (!r) + return t.dom.getRoot(); + + e = r.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!r.collapsed) { + // If the anchor node is a element instead of a text node then return this element + if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1) + return s.anchorNode.childNodes[s.anchorOffset]; + + if (r.startContainer == r.endContainer) { + if (r.startOffset - r.endOffset < 2) { + if (r.startContainer.hasChildNodes()) + e = r.startContainer.childNodes[r.startOffset]; + } + } + } + + return t.dom.getParent(e, '*'); + } + + return r.item ? r.item(0) : r.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('' + n + '>'); + + if (this.settings.indentation > 0) + this.writeRaw('\n'); + } + }, + + writeFullEndElement : function() { + if (this.tags.length > 0) { + this._writeAttributesEnd(); + this.writeRaw('' + this.tags.pop() + '>'); + + 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', + bool_attrs : /(checked|disabled|readonly|selected|nowrap)/, + valid_elements : '*[*]', + extended_valid_elements : 0, + valid_child_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, + font_size_style_values : 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; + + 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 '' + c + '>'; + + 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 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 ]*>)(.*?)(<\/script>)/g}, + {pattern : /(]*>)(.*?)(<\/noscript>)/g}, + {pattern : /("});j=j.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(h,l,k){return""})}j=j.replace(//g,"");j=j.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi,function(h,l){function k(o,n,q){var p=q;if(h.indexOf("mce_"+n)!=-1){return o}if(n=="style"){if(g._isRes(q)){return o}if(i.hex_colors){p=p.replace(/rgb\([^\)]+\)/g,function(m){return g.toHex(m)})}if(i.url_converter){p=p.replace(/url\([\'\"]?([^\)\'\"]+)\)/g,function(m,r){return"url("+g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(r),n,l))+")"})}}else{if(n!="coords"&&n!="shape"){if(i.url_converter){p=g.encode(i.url_converter.call(i.url_converter_scope||g,g.decode(q),n,l))}}}return" "+n+'="'+q+'" mce_'+n+'="'+p+'"'}h=h.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi,k);h=h.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi,k);return h.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi,k)})}return j},getOuterHTML:function(f){var g;f=this.get(f);if(!f){return null}if(f.outerHTML!==undefined){return f.outerHTML}g=(f.ownerDocument||this.doc).createElement("body");g.appendChild(f.cloneNode(true));return g.innerHTML},setOuterHTML:function(i,g,j){var f=this;return this.run(i,function(h){var l,k;h=f.get(h);j=j||h.ownerDocument||f.doc;if(a&&h.nodeType==1){h.outerHTML=g}else{k=j.createElement("body");k.innerHTML=g;l=k.lastChild;while(l){f.insertAfter(l.cloneNode(true),h);l=l.previousSibling}f.remove(h)}})},decode:function(g){var h,i,f;if(/&[^;]+;/.test(g)){h=this.doc.createElement("div");h.innerHTML=g;i=h.firstChild;f="";if(i){do{f+=i.nodeValue}while(i.nextSibling)}return f||g}return g},encode:function(f){return f?(""+f).replace(/[<>&\"]/g,function(h,g){switch(h){case"&":return"&";case'"':return""";case"<":return"<";case">":return">"}return h}):f},insertAfter:function(h,g){var f=this;g=f.get(g);return this.run(h,function(k){var j,i;j=g.parentNode;i=g.nextSibling;if(i){j.insertBefore(k,i)}else{j.appendChild(k)}return k})},isBlock:function(f){if(f.nodeType&&f.nodeType!==1){return false}f=f.nodeName||f;return/^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(f)},replace:function(i,h,f){var g=this;if(b(h,"array")){i=i.cloneNode(true)}return g.run(h,function(j){if(f){e(j.childNodes,function(k){i.appendChild(k.cloneNode(true))})}if(g.fixPsuedoLeaks&&j.nodeType===1){j.parentNode.insertBefore(i,j);g.remove(j);return i}return j.parentNode.replaceChild(i,j)})},findCommonAncestor:function(h,f){var i=h,g;while(i){g=f;while(g&&i!=g){g=g.parentNode}if(i==g){break}i=i.parentNode}if(!i&&h.ownerDocument){return h.ownerDocument.documentElement}return i},toHex:function(f){var h=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(f);function g(i){i=parseInt(i).toString(16);return i.length>1?i:"0"+i}if(h){f="#"+g(h[1])+g(h[2])+g(h[3]);return f}return f},getClasses:function(){var l=this,g=[],k,m={},n=l.settings.class_filter,j;if(l.classes){return l.classes}function o(f){e(f.imports,function(i){o(i)});e(f.cssRules||f.rules,function(i){switch(i.type||1){case 1:if(i.selectorText){e(i.selectorText.split(","),function(p){p=p.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(p)||!/\.[\w\-]+$/.test(p)){return}j=p;p=p.replace(/.*\.([a-z0-9_\-]+).*/i,"$1");if(n&&!(p=n(p,j))){return}if(!m[p]){g.push({"class":p});m[p]=1}})}break;case 3:o(i.styleSheet);break}})}try{e(l.doc.styleSheets,o)}catch(h){}if(g.length>0){l.classes=g}return g},run:function(j,i,h){var g=this,k;if(g.doc&&typeof(j)==="string"){j=g.get(j)}if(!j){return false}h=h||this;if(!j.nodeType&&(j.length||j.length===0)){k=[];e(j,function(l,f){if(l){if(typeof(l)=="string"){l=g.doc.getElementById(l)}k.push(i.call(h,l,f))}});return k}return i.call(h,j)},getAttribs:function(g){var f;g=this.get(g);if(!g){return[]}if(a){f=[];if(g.nodeName=="OBJECT"){return g.attributes}g.cloneNode(false).outerHTML.replace(/([a-z0-9\:\-_]+)=/gi,function(i,h){f.push({specified:1,nodeName:h})});return f}return g.attributes},destroy:function(g){var f=this;if(f.events){f.events.destroy()}f.win=f.doc=f.root=f.events=null;if(!g){c.removeUnload(f.destroy)}},createRng:function(){var f=this.doc;return f.createRange?f.createRange():new c.dom.Range(this)},split:function(k,j,n){var o=this,f=o.createRng(),l,i,m;function g(q,p){q=q[p];if(q&&q[p]&&q[p].nodeType==1&&h(q[p])){o.remove(q[p])}}function h(p){p=o.getOuterHTML(p);p=p.replace(/<(img|hr|table)/gi,"-");p=p.replace(/<[^>]+>/g,"");return p.replace(/[ \t\r\n]+| | /g,"")==""}if(k&&j){f.setStartBefore(k);f.setEndBefore(j);l=f.extractContents();f=o.createRng();f.setStartAfter(j);f.setEndAfter(k);i=f.extractContents();m=k.parentNode;g(l,"lastChild");if(!h(l)){m.insertBefore(l,k)}if(n){m.replaceChild(n,j)}else{m.insertBefore(j,k)}g(i,"firstChild");if(!h(i)){m.insertBefore(i,k)}o.remove(k);return n||j}},bind:function(j,f,i,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.add(j,f,i,h||this)},unbind:function(i,f,h){var g=this;if(!g.events){g.events=new c.dom.EventUtils()}return g.events.remove(i,f,h)},_isRes:function(f){return/^(top|left|bottom|right|width|height)/i.test(f)||/;\s*(top|left|bottom|right|width|height)/i.test(f)}});c.DOM=new c.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(f){var h=0,c=1,e=2,d=tinymce.extend;function g(m,k){var j,l;if(m.parentNode!=k){return -1}for(l=k.firstChild,j=0;l!=m;l=l.nextSibling){j++}return j}function b(k){var j=0;while(k.previousSibling){j++;k=k.previousSibling}return j}function i(j,k){var l;if(j.nodeType==3){return j}if(k<0){return j}l=j.firstChild;while(l!=null&&k>0){--k;l=l.nextSibling}if(l!=null){return l}return j}function a(k){var j=k.doc;d(this,{dom:k,startContainer:j,startOffset:0,endContainer:j,endOffset:0,collapsed:true,commonAncestorContainer:j,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3})}d(a.prototype,{setStart:function(k,j){this._setEndPoint(true,k,j)},setEnd:function(k,j){this._setEndPoint(false,k,j)},setStartBefore:function(j){this.setStart(j.parentNode,b(j))},setStartAfter:function(j){this.setStart(j.parentNode,b(j)+1)},setEndBefore:function(j){this.setEnd(j.parentNode,b(j))},setEndAfter:function(j){this.setEnd(j.parentNode,b(j)+1)},collapse:function(k){var j=this;if(k){j.endContainer=j.startContainer;j.endOffset=j.startOffset}else{j.startContainer=j.endContainer;j.startOffset=j.endOffset}j.collapsed=true},selectNode:function(j){this.setStartBefore(j);this.setEndAfter(j)},selectNodeContents:function(j){this.setStart(j,0);this.setEnd(j,j.nodeType===1?j.childNodes.length:j.nodeValue.length)},compareBoundaryPoints:function(m,n){var l=this,p=l.startContainer,o=l.startOffset,k=l.endContainer,j=l.endOffset;if(m===0){return l._compareBoundaryPoints(p,o,p,o)}if(m===1){return l._compareBoundaryPoints(p,o,k,j)}if(m===2){return l._compareBoundaryPoints(k,j,k,j)}if(m===3){return l._compareBoundaryPoints(k,j,p,o)}},deleteContents:function(){this._traverse(e)},extractContents:function(){return this._traverse(h)},cloneContents:function(){return this._traverse(c)},insertNode:function(m){var j=this,l,k;if(m.nodeType===3||m.nodeType===4){l=j.startContainer.splitText(j.startOffset);j.startContainer.parentNode.insertBefore(m,l)}else{if(j.startContainer.childNodes.length>0){k=j.startContainer.childNodes[j.startOffset]}j.startContainer.insertBefore(m,k)}},surroundContents:function(l){var j=this,k=j.extractContents();j.insertNode(l);l.appendChild(k);j.selectNode(l)},cloneRange:function(){var j=this;return d(new a(j.dom),{startContainer:j.startContainer,startOffset:j.startOffset,endContainer:j.endContainer,endOffset:j.endOffset,collapsed:j.collapsed,commonAncestorContainer:j.commonAncestorContainer})},_isCollapsed:function(){return(this.startContainer==this.endContainer&&this.startOffset==this.endOffset)},_compareBoundaryPoints:function(m,p,k,o){var q,l,j,r,t,s;if(m==k){if(p==o){return 0}else{if(p0){l.collapse(k)}}l.collapsed=l._isCollapsed();l.commonAncestorContainer=l.dom.findCommonAncestor(l.startContainer,l.endContainer)},_traverse:function(r){var s=this,q,m=0,v=0,k,o,l,n,j,u;if(s.startContainer==s.endContainer){return s._traverseSameContainer(r)}for(q=s.endContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.startContainer){return s._traverseCommonStartContainer(q,r)}++m}for(q=s.startContainer,k=q.parentNode;k!=null;q=k,k=k.parentNode){if(k==s.endContainer){return s._traverseCommonEndContainer(q,r)}++v}o=v-m;l=s.startContainer;while(o>0){l=l.parentNode;o--}n=s.endContainer;while(o<0){n=n.parentNode;o++}for(j=l.parentNode,u=n.parentNode;j!=u;j=j.parentNode,u=u.parentNode){l=j;n=u}return s._traverseCommonAncestors(l,n,r)},_traverseSameContainer:function(o){var r=this,q,u,j,k,l,p,m;if(o!=e){q=r.dom.doc.createDocumentFragment()}if(r.startOffset==r.endOffset){return q}if(r.startContainer.nodeType==3){u=r.startContainer.nodeValue;j=u.substring(r.startOffset,r.endOffset);if(o!=c){r.startContainer.deleteData(r.startOffset,r.endOffset-r.startOffset);r.collapse(true)}if(o==e){return null}q.appendChild(r.dom.doc.createTextNode(j));return q}k=i(r.startContainer,r.startOffset);l=r.endOffset-r.startOffset;while(l>0){p=k.nextSibling;m=r._traverseFullySelected(k,o);if(q){q.appendChild(m)}--l;k=p}if(o!=c){r.collapse(true)}return q},_traverseCommonStartContainer:function(j,p){var s=this,r,k,l,m,q,o;if(p!=e){r=s.dom.doc.createDocumentFragment()}k=s._traverseRightBoundary(j,p);if(r){r.appendChild(k)}l=g(j,s.startContainer);m=l-s.startOffset;if(m<=0){if(p!=c){s.setEndBefore(j);s.collapse(false)}return r}k=j.previousSibling;while(m>0){q=k.previousSibling;o=s._traverseFullySelected(k,p);if(r){r.insertBefore(o,r.firstChild)}--m;k=q}if(p!=c){s.setEndBefore(j);s.collapse(false)}return r},_traverseCommonEndContainer:function(m,p){var s=this,r,o,j,k,q,l;if(p!=e){r=s.dom.doc.createDocumentFragment()}j=s._traverseLeftBoundary(m,p);if(r){r.appendChild(j)}o=g(m,s.endContainer);++o;k=s.endOffset-o;j=m.nextSibling;while(k>0){q=j.nextSibling;l=s._traverseFullySelected(j,p);if(r){r.appendChild(l)}--k;j=q}if(p!=c){s.setStartAfter(m);s.collapse(true)}return r},_traverseCommonAncestors:function(p,j,s){var w=this,l,v,o,q,r,k,u,m;if(s!=e){v=w.dom.doc.createDocumentFragment()}l=w._traverseLeftBoundary(p,s);if(v){v.appendChild(l)}o=p.parentNode;q=g(p,o);r=g(j,o);++q;k=r-q;u=p.nextSibling;while(k>0){m=u.nextSibling;l=w._traverseFullySelected(u,s);if(v){v.appendChild(l)}u=m;--k}l=w._traverseRightBoundary(j,s);if(v){v.appendChild(l)}if(s!=c){w.setStartAfter(p);w.collapse(true)}return v},_traverseRightBoundary:function(p,q){var s=this,l=i(s.endContainer,s.endOffset-1),r,o,n,j,k;var m=l!=s.endContainer;if(l==p){return s._traverseNode(l,m,false,q)}r=l.parentNode;o=s._traverseNode(r,false,false,q);while(r!=null){while(l!=null){n=l.previousSibling;j=s._traverseNode(l,m,false,q);if(q!=e){o.insertBefore(j,o.firstChild)}m=true;l=n}if(r==p){return o}l=r.previousSibling;r=r.parentNode;k=s._traverseNode(r,false,false,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseLeftBoundary:function(p,q){var s=this,m=i(s.startContainer,s.startOffset);var n=m!=s.startContainer,r,o,l,j,k;if(m==p){return s._traverseNode(m,n,true,q)}r=m.parentNode;o=s._traverseNode(r,false,true,q);while(r!=null){while(m!=null){l=m.nextSibling;j=s._traverseNode(m,n,true,q);if(q!=e){o.appendChild(j)}n=true;m=l}if(r==p){return o}m=r.nextSibling;r=r.parentNode;k=s._traverseNode(r,false,true,q);if(q!=e){k.appendChild(o)}o=k}return null},_traverseNode:function(j,o,r,s){var u=this,m,l,p,k,q;if(o){return u._traverseFullySelected(j,s)}if(j.nodeType==3){m=j.nodeValue;if(r){k=u.startOffset;l=m.substring(k);p=m.substring(0,k)}else{k=u.endOffset;l=m.substring(0,k);p=m.substring(k)}if(s!=c){j.nodeValue=p}if(s==e){return null}q=j.cloneNode(false);q.nodeValue=l;return q}if(s==e){return null}return j.cloneNode(false)},_traverseFullySelected:function(l,k){var j=this;if(k!=e){return k==c?l.cloneNode(true):l}l.parentNode.removeChild(l);return null}});f.Range=a})(tinymce.dom);(function(){function a(e){var d=this,h="\uFEFF",b,g;function c(j,i){if(j&&i){if(j.item&&i.item&&j.item(0)===i.item(0)){return 1}if(j.isEqual&&i.isEqual&&i.isEqual(j)){return 1}}return 0}function f(){var m=e.dom,j=e.getRng(),s=m.createRng(),p,k,n,q,o,l;function i(v){var t=v.parentNode.childNodes,u;for(u=t.length-1;u>=0;u--){if(t[u]==v){return u}}return -1}function r(v){var t=j.duplicate(),B,y,u,w,x=0,z=0,A,C;t.collapse(v);B=t.parentElement();t.pasteHTML(h);u=B.childNodes;for(y=0;y0&&(w.nodeType!==3||u[y-1].nodeType!==3)){z++}if(w.nodeType===3){A=w.nodeValue.indexOf(h);if(A!==-1){x+=A;break}x+=w.nodeValue.length}else{x=0}}t.moveStart("character",-1);t.text="";return{index:z,offset:x,parent:B}}n=j.item?j.item(0):j.parentElement();if(n.ownerDocument!=m.doc){return s}if(j.item||!n.hasChildNodes()){s.setStart(n.parentNode,i(n));s.setEnd(s.startContainer,s.startOffset+1);return s}l=e.isCollapsed();p=r(true);k=r(false);p.parent.normalize();k.parent.normalize();q=p.parent.childNodes[Math.min(p.index,p.parent.childNodes.length-1)];if(q.nodeType!=3){s.setStart(p.parent,p.index)}else{s.setStart(p.parent.childNodes[p.index],p.offset)}o=k.parent.childNodes[Math.min(k.index,k.parent.childNodes.length-1)];if(o.nodeType!=3){if(!l){k.index++}s.setEnd(k.parent,k.index)}else{s.setEnd(k.parent.childNodes[k.index],k.offset)}if(!l){q=s.startContainer;if(q.nodeType==1){s.setStart(q,Math.min(s.startOffset,q.childNodes.length))}o=s.endContainer;if(o.nodeType==1){s.setEnd(o,Math.min(s.endOffset,o.childNodes.length))}}d.addRange(s);return s}this.addRange=function(j){var o,m=e.dom.doc.body,p,k,q,l,n,i;q=j.startContainer;l=j.startOffset;n=j.endContainer;i=j.endOffset;o=m.createTextRange();q=q.nodeType==1?q.childNodes[Math.min(l,q.childNodes.length-1)]:q;n=n.nodeType==1?n.childNodes[Math.min(l==i?i:i-1,n.childNodes.length-1)]:n;if(q==n&&q.nodeType==1){if(/^(IMG|TABLE)$/.test(q.nodeName)&&l!=i){o=m.createControlRange();o.addElement(q)}else{o=m.createTextRange();if(!q.hasChildNodes()&&q.canHaveHTML){q.innerHTML=h}o.moveToElementText(q);if(q.innerHTML==h){o.collapse(true);q.removeChild(q.firstChild)}}if(l==i){o.collapse(i<=j.endContainer.childNodes.length-1)}o.select();return}function r(t,v){var u,s,w;if(t.nodeType!=3){return -1}u=t.nodeValue;s=m.createTextRange();t.nodeValue=u.substring(0,v)+h+u.substring(v);s.moveToElementText(t.parentNode);s.findText(h);w=Math.abs(s.moveStart("character",-1048575));t.nodeValue=u;return w}if(j.collapsed){pos=r(q,l);o=m.createTextRange();o.move("character",pos);o.select();return}else{if(q==n&&q.nodeType==3){p=r(q,l);o.move("character",p);o.moveEnd("character",i-l);o.select();return}p=r(q,l);k=r(n,i);o=m.createTextRange();if(p==-1){o.moveToElementText(q);p=0}else{o.move("character",p)}tmpRng=m.createTextRange();if(k==-1){tmpRng.moveToElementText(n)}else{tmpRng.move("character",k)}o.setEndPoint("EndToEnd",tmpRng);o.select();return}};this.getRangeAt=function(){if(!b||!c(g,e.getRng())){b=f();g=e.getRng()}return b};this.destroy=function(){g=b=null}}tinymce.dom.TridentSelection=a})();(function(){var p=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,i=0,d=Object.prototype.toString,n=false;var b=function(D,t,A,v){A=A||[];var e=t=t||document;if(t.nodeType!==1&&t.nodeType!==9){return[]}if(!D||typeof D!=="string"){return A}var B=[],C,y,G,F,z,s,r=true,w=o(t);p.lastIndex=0;while((C=p.exec(D))!==null){B.push(C[1]);if(C[2]){s=RegExp.rightContext;break}}if(B.length>1&&j.exec(D)){if(B.length===2&&f.relative[B[0]]){y=g(B[0]+B[1],t)}else{y=f.relative[B[0]]?[t]:b(B.shift(),t);while(B.length){D=B.shift();if(f.relative[D]){D+=B.shift()}y=g(D,y)}}}else{if(!v&&B.length>1&&t.nodeType===9&&!w&&f.match.ID.test(B[0])&&!f.match.ID.test(B[B.length-1])){var H=b.find(B.shift(),t,w);t=H.expr?b.filter(H.expr,H.set)[0]:H.set[0]}if(t){var H=v?{expr:B.pop(),set:a(v)}:b.find(B.pop(),B.length===1&&(B[0]==="~"||B[0]==="+")&&t.parentNode?t.parentNode:t,w);y=H.expr?b.filter(H.expr,H.set):H.set;if(B.length>0){G=a(y)}else{r=false}while(B.length){var u=B.pop(),x=u;if(!f.relative[u]){u=""}else{x=B.pop()}if(x==null){x=t}f.relative[u](G,x,w)}}else{G=B=[]}}if(!G){G=y}if(!G){throw"Syntax error, unrecognized expression: "+(u||D)}if(d.call(G)==="[object Array]"){if(!r){A.push.apply(A,G)}else{if(t&&t.nodeType===1){for(var E=0;G[E]!=null;E++){if(G[E]&&(G[E]===true||G[E].nodeType===1&&h(t,G[E]))){A.push(y[E])}}}else{for(var E=0;G[E]!=null;E++){if(G[E]&&G[E].nodeType===1){A.push(y[E])}}}}}else{a(G,A)}if(s){b(s,e,A,v);b.uniqueSort(A)}return A};b.uniqueSort=function(r){if(c){n=false;r.sort(c);if(n){for(var e=1;e":function(w,r,x){var u=typeof r==="string";if(u&&!/\W/.test(r)){r=x?r:r.toUpperCase();for(var s=0,e=w.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,w){var t=u[1].replace(/\\/g,"");if(!w&&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(w,s,t,x){var r=s[1],u=f.filters[r];if(u){return u(w,t,s,x)}else{if(r==="contains"){return(w.textContent||w.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),w=e+"",u=t[2],r=t[4];return e==null?u==="!=":u==="="?w===r:u==="*="?w.indexOf(r)>=0:u==="~="?(" "+w+" ").indexOf(r)>=0:!r?w&&e!==false:u==="!="?w!=r:u==="^="?w.indexOf(r)===0:u==="$="?w.substr(w.length-r.length)===r:u==="|="?w===r||w.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,w){if(typeof v.getElementById!=="undefined"&&!w){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(w,v,t,u){v=v||document;if(!u&&v.nodeType===9&&!o(v)){try{return a(v.querySelectorAll(w),t)}catch(x){}}return e(w,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,w,v,A,x,z){var y=r=="previousSibling"&&!z;for(var t=0,s=A.length;t0){u=e;break}}}e=e[r]}A[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,x){var t=[],u="",v,s=x.nodeType?[x]:x;while((v=f.match.PSEUDO.exec(e))){u+=v[0];e=e.replace(f.match.PSEUDO,"")}e=f.relative[e]?e+"*":e;for(var w=0,r=s.length;w=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){var b=a.each;a.create("tinymce.dom.Element",{Element:function(g,e){var c=this,f,d;e=e||{};c.id=g;c.dom=f=e.dom||a.DOM;c.settings=e;if(!a.isIE){d=c.dom.get(c.id)}b(["getPos","getRect","getParent","add","setStyle","getStyle","setStyles","setAttrib","setAttribs","getAttrib","addClass","removeClass","hasClass","getOuterHTML","setOuterHTML","remove","show","hide","isHidden","setHTML","get"],function(h){c[h]=function(){var j=[g],k;for(k=0;k_';j.deleteContents();j.insertNode(f.getRng().createContextualFragment(i));l=f.dom.get("__caret");j=k.createRange();j.setStartBefore(l);j.setEndAfter(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(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(1);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.firstChild}return h}else{h=g.startContainer;if(h.nodeName=="BODY"){return h.firstChild}return f.dom.getParent(h,"*")}},getEnd:function(){var f=this,g=f.getRng(),h;if(a){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(0);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.lastChild}return h}else{h=g.endContainer;if(h.nodeName=="BODY"){return h.lastChild}return f.dom.getParent(h,"*")}},getBookmark:function(x){var j=this,m=j.getRng(),f,n,l,u=j.dom.getViewPort(j.win),v,p,z,o,w=-16777215,k,h=j.dom.getRoot(),g=0,i=0,y;n=u.x;l=u.y;if(x){return{rng:m,scrollX:n,scrollY:l}}if(a){if(m.item){v=m.item(0);d(j.dom.select(v.nodeName),function(s,r){if(v==s){p=r;return false}});return{tag:v.nodeName,index:p,scrollX:n,scrollY:l}}f=j.dom.doc.body.createTextRange();f.moveToElementText(h);f.collapse(true);z=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(true);p=Math.abs(f.move("character",w));f=m.duplicate();f.collapse(false);o=Math.abs(f.move("character",w))-p;return{start:p-z,length:o,scrollX:n,scrollY:l}}v=j.getNode();k=j.getSel();if(!k){return null}if(v&&v.nodeName=="IMG"){return{scrollX:n,scrollY:l}}function q(A,D,t){var s=j.dom.doc.createTreeWalker(A,NodeFilter.SHOW_TEXT,null,false),E,B=0,C={};while((E=s.nextNode())!=null){if(E==D){C.start=B}if(E==t){C.end=B;return C}B+=e(E.nodeValue||"").length}return null}if(k.anchorNode==k.focusNode&&k.anchorOffset==k.focusOffset){v=q(h,k.anchorNode,k.focusNode);if(!v){return{scrollX:n,scrollY:l}}e(k.anchorNode.nodeValue||"").replace(/^\s+/,function(r){g=r.length});return{start:Math.max(v.start+k.anchorOffset-g,0),end:Math.max(v.end+k.focusOffset-g,0),scrollX:n,scrollY:l,beg:k.anchorOffset-g==0}}else{v=q(h,m.startContainer,m.endContainer);if(!v){return{scrollX:n,scrollY:l}}return{start:Math.max(v.start+m.startOffset-g,0),end:Math.max(v.end+m.endOffset-i,0),scrollX:n,scrollY:l,beg:m.startOffset-g==0}}},moveToBookmark:function(n){var o=this,g=o.getRng(),p=o.getSel(),j=o.dom.getRoot(),m,h,k;function i(q,t,D){var B=o.dom.doc.createTreeWalker(q,NodeFilter.SHOW_TEXT,null,false),x,s=0,A={},u,C,z,y;while((x=B.nextNode())!=null){z=y=0;k=x.nodeValue||"";h=e(k).length;s+=h;if(s>=t&&!A.startNode){u=t-(s-h);if(n.beg&&u>=h){continue}A.startNode=x;A.startOffset=u+y}if(s>=D){A.endNode=x;A.endOffset=D-(s-h)+y;return A}}return null}if(!n){return false}o.win.scrollTo(n.scrollX,n.scrollY);if(a){if(g=n.rng){try{g.select()}catch(l){}return true}o.win.focus();if(n.tag){g=j.createControlRange();d(o.dom.select(n.tag),function(r,q){if(q==n.index){g.addElement(r)}})}else{try{if(n.start<0){return true}g=p.createRange();g.moveToElementText(j);g.collapse(true);g.moveStart("character",n.start);g.moveEnd("character",n.length)}catch(f){return true}}try{g.select()}catch(l){}return true}if(!p){return false}if(n.rng){p.removeAllRanges();p.addRange(n.rng)}else{if(b(n.start)&&b(n.end)){try{m=i(j,n.start,n.end);if(m){g=o.dom.doc.createRange();g.setStart(m.startNode,m.startOffset);g.setEnd(m.endNode,m.endOffset);p.removeAllRanges();p.addRange(g)}if(!c.isOpera){o.win.focus()}}catch(l){}}}},select:function(g,l){var p=this,f=p.getRng(),q=p.getSel(),o,m,k,j=p.win.document;function h(u,t){var s,r;if(u){s=j.createTreeWalker(u,NodeFilter.SHOW_TEXT,null,false);while(u=s.nextNode()){r=u;if(c.trim(u.nodeValue).length!=0){if(t){return u}else{r=u}}}}return r}if(a){try{o=j.body;if(/^(IMG|TABLE)$/.test(g.nodeName)){f=o.createControlRange();f.addElement(g)}else{f=o.createTextRange();f.moveToElementText(g)}f.select()}catch(i){}}else{if(l){m=h(g,1)||p.dom.select("br:first",g)[0];k=h(g,0)||p.dom.select("br:last",g)[0];if(m&&k){f=j.createRange();if(m.nodeName=="BR"){f.setStartBefore(m)}else{f.setStart(m,0)}if(k.nodeName=="BR"){f.setEndBefore(k)}else{f.setEnd(k,k.nodeValue.length)}}else{f.selectNode(g)}}else{f.selectNode(g)}p.setRng(f)}return g},isCollapsed:function(){var f=this,h=f.getRng(),g=f.getSel();if(!h||h.item){return false}return !g||h.boundingWidth==0||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=a?g.win.document.body.createTextRange():g.win.document.createRange()}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 f=this,h=f.getRng(),g=f.getSel(),i;if(!a){if(!h){return f.dom.getRoot()}i=h.commonAncestorContainer;if(!h.collapsed){if(c.isWebKit&&g.anchorNode&&g.anchorNode.nodeType==1){return g.anchorNode.childNodes[g.anchorOffset]}if(h.startContainer==h.endContainer){if(h.startOffset-h.endOffset<2){if(h.startContainer.hasChildNodes()){i=h.startContainer.childNodes[h.startOffset]}}}}return f.dom.getParent(i,"*")}return h.item?h.item(0):h.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(""+b+">")}if(this.settings.indentation>0){this.writeRaw("\n")}}},writeFullEndElement:function(){if(this.tags.length>0){this._writeAttributesEnd();this.writeRaw(""+this.tags.pop()+">");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",bool_attrs:/(checked|disabled|readonly|selected|nowrap)/,valid_elements:"*[*]",extended_valid_elements:0,valid_child_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,font_size_style_values: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;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""+o+">"}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,y,w=["ol","ul"],u,t,q,k=/^(OL|UL)$/,z;function m(r,x){var o=x.split(","),p;while((r=r.previousSibling)!=null){for(p=0;p1){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]*>)(.*?)(<\/script>)/g},{pattern:/(]*>)(.*?)(<\/noscript>)/g},{pattern:/('; + }); + + // Wrap noscript elements + h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { + return ''; + }); + } + + h = h.replace(//g, ''); + + // Process all tags with src, href or style + h = h.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi, function(a, n) { + function handle(m, b, c) { + var u = c; + + // Tag already got a mce_ version + if (a.indexOf('mce_' + b) != -1) + return m; + + if (b == 'style') { + // No mce_style for elements with these since they might get resized by the user + if (t._isRes(c)) + return m; + + if (s.hex_colors) { + u = u.replace(/rgb\([^\)]+\)/g, function(v) { + return t.toHex(v); + }); + } + + if (s.url_converter) { + u = u.replace(/url\([\'\"]?([^\)\'\"]+)\)/g, function(x, c) { + return 'url(' + t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)) + ')'; + }); + } + } else if (b != 'coords' && b != 'shape') { + if (s.url_converter) + u = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)); + } + + return ' ' + b + '="' + c + '" mce_' + b + '="' + u + '"'; + }; + + a = a.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi, handle); // W3C + a = a.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi, handle); // W3C + + return a.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi, handle); // IE + }); + } + + 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; + + return this.run(e, function(e) { + var n, tp; + + e = t.get(e); + d = d || e.ownerDocument || t.doc; + + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else { + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + } + }); + }, + + decode : function(s) { + var e, n, v; + + // Look for entities to decode + if (/&[^;]+;/.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.nextSibling); + } + + return v || s; + } + + return s; + }, + + encode : function(s) { + return s ? ('' + s).replace(/[<>&\"]/g, function (c, b) { + switch (c) { + case '&': + return '&'; + + case '"': + return '"'; + + case '<': + return '<'; + + case '>': + return '>'; + } + + return c; + }) : s; + }, + + insertAfter : function(n, r) { + var t = this; + + r = t.get(r); + + return this.run(n, function(n) { + var p, ns; + + p = r.parentNode; + ns = r.nextSibling; + + if (ns) + p.insertBefore(n, ns); + else + p.appendChild(n); + + return n; + }); + }, + + isBlock : function(n) { + if (n.nodeType && n.nodeType !== 1) + return false; + + n = n.nodeName || n; + + return /^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.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(o.childNodes, function(c) { + n.appendChild(c.cloneNode(true)); + }); + } + + // Fix IE psuedo leak for elements since replacing elements if fairly common + // Will break parentNode for some unknown reason + if (t.fixPsuedoLeaks && o.nodeType === 1) { + o.parentNode.insertBefore(n, o); + t.remove(o); + return n; + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + 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; + + // 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(/([a-z0-9\:\-_]+)=/gi, function(a, b) { + o.push({specified : 1, nodeName : b}); + }); + + 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); + }, + + 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 sence + // but we don't want that in our code since it serves no purpose + // For example if this is chopped: + // text 1CHOPtext 2 + // would produce: + // text 1CHOPtext 2 + // this function will then trim of empty edges and produce: + // text 1CHOPtext 2 + function trimEdge(n, na) { + n = n[na]; + + if (n && n[na] && n[na].nodeType == 1 && isEmpty(n[na])) + t.remove(n[na]); + }; + + function isEmpty(n) { + n = t.getOuterHTML(n); + n = n.replace(/<(img|hr|table)/gi, '-'); // Keep these convert them to - chars + n = n.replace(/<[^>]+>/g, ''); // Remove all tags + + return n.replace(/[ \t\r\n]+| | /g, '') == ''; + }; + + if (pe && e) { + // Get before chunk + r.setStartBefore(pe); + r.setEndBefore(e); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStartAfter(e); + r.setEndAfter(pe); + aft = r.extractContents(); + + // Insert chunks and remove parent + pa = pe.parentNode; + + // Remove right side edge of the before contents + trimEdge(bef, 'lastChild'); + + if (!isEmpty(bef)) + pa.insertBefore(bef, pe); + + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Remove left site edge of the after contents + trimEdge(aft, 'firstChild'); + + if (!isEmpty(aft)) + pa.insertBefore(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); + }, + + + _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; + } + */ + + }); + + // Setup page DOM + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); +(function(ns) { + // Traverse constants + var EXTRACT = 0, CLONE = 1, DELETE = 2, extend = tinymce.extend; + + function indexOf(child, parent) { + var i, node; + + if (child.parentNode != parent) + return -1; + + for (node = parent.firstChild, i = 0; node != child; node = node.nextSibling) + i++; + + return i; + }; + + function nodeIndex(n) { + var i = 0; + + while (n.previousSibling) { + i++; + n = n.previousSibling; + } + + return i; + }; + + function getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child != null && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child != null) + return child; + + return container; + }; + + // Range constructor + function Range(dom) { + var d = dom.doc; + + extend(this, { + dom : dom, + + // Inital states + startContainer : d, + startOffset : 0, + endContainer : d, + endOffset : 0, + collapsed : true, + commonAncestorContainer : d, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3 + }); + }; + + // Add range methods + extend(Range.prototype, { + setStart : function(n, o) { + this._setEndPoint(true, n, o); + }, + + setEnd : function(n, o) { + this._setEndPoint(false, n, o); + }, + + setStartBefore : function(n) { + this.setStart(n.parentNode, nodeIndex(n)); + }, + + setStartAfter : function(n) { + this.setStart(n.parentNode, nodeIndex(n) + 1); + }, + + setEndBefore : function(n) { + this.setEnd(n.parentNode, nodeIndex(n)); + }, + + setEndAfter : function(n) { + this.setEnd(n.parentNode, nodeIndex(n) + 1); + }, + + collapse : function(ts) { + var t = this; + + if (ts) { + t.endContainer = t.startContainer; + t.endOffset = t.startOffset; + } else { + t.startContainer = t.endContainer; + t.startOffset = t.endOffset; + } + + t.collapsed = true; + }, + + selectNode : function(n) { + this.setStartBefore(n); + this.setEndAfter(n); + }, + + selectNodeContents : function(n) { + this.setStart(n, 0); + this.setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }, + + compareBoundaryPoints : function(h, r) { + var t = this, sc = t.startContainer, so = t.startOffset, ec = t.endContainer, eo = t.endOffset; + + // Check START_TO_START + if (h === 0) + return t._compareBoundaryPoints(sc, so, sc, so); + + // Check START_TO_END + if (h === 1) + return t._compareBoundaryPoints(sc, so, ec, eo); + + // Check END_TO_END + if (h === 2) + return t._compareBoundaryPoints(ec, eo, ec, eo); + + // Check END_TO_START + if (h === 3) + return t._compareBoundaryPoints(ec, eo, sc, so); + }, + + deleteContents : function() { + this._traverse(DELETE); + }, + + extractContents : function() { + return this._traverse(EXTRACT); + }, + + cloneContents : function() { + return this._traverse(CLONE); + }, + + insertNode : function(n) { + var t = this, nn, o; + + // Node is TEXT_NODE or CDATA + if (n.nodeType === 3 || n.nodeType === 4) { + nn = t.startContainer.splitText(t.startOffset); + t.startContainer.parentNode.insertBefore(n, nn); + } else { + // Insert element node + if (t.startContainer.childNodes.length > 0) + o = t.startContainer.childNodes[t.startOffset]; + + t.startContainer.insertBefore(n, o); + } + }, + + surroundContents : function(n) { + var t = this, f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }, + + cloneRange : function() { + var t = this; + + return extend(new Range(t.dom), { + startContainer : t.startContainer, + startOffset : t.startOffset, + endContainer : t.endContainer, + endOffset : t.endOffset, + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }, + +/* + detach : function() { + // Not implemented + }, +*/ + // Internal methods + + _isCollapsed : function() { + return (this.startContainer == this.endContainer && this.startOffset == this.endOffset); + }, + + _compareBoundaryPoints : function (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 + } else if (offsetA < offsetB) { + return -1; // before + } else { + 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 + } else { + 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 + } else { + 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 = this.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; + } + }, + + _setEndPoint : function(st, n, o) { + var t = this, ec, sc; + + if (st) { + t.startContainer = n; + t.startOffset = o; + } else { + t.endContainer = n; + t.endOffset = 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.endContainer; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t.startContainer; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc != ec) { + t.collapse(st); + } else { + // 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 (t._compareBoundaryPoints(t.startContainer, t.startOffset, t.endContainer, t.endOffset) > 0) + t.collapse(st); + } + + t.collapsed = t._isCollapsed(); + t.commonAncestorContainer = t.dom.findCommonAncestor(t.startContainer, t.endContainer); + }, + + // This code is heavily "inspired" by the Apache Xerces implementation. I hope they don't mind. :) + + _traverse : function(how) { + var t = this, c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t.startContainer == t.endContainer) + return t._traverseSameContainer(how); + + for (c = t.endContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.startContainer) + return t._traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t.startContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.endContainer) + return t._traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t.startContainer; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t.endContainer; + 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 t._traverseCommonAncestors(startNode, endNode, how); + }, + + _traverseSameContainer : function(how) { + var t = this, frag, s, sub, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t.startOffset == t.endOffset) + return frag; + + // Text node needs special case handling + if (t.startContainer.nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t.startContainer.nodeValue; + sub = s.substring(t.startOffset, t.endOffset); + + // set the original text node to its new value + if (how != CLONE) { + t.startContainer.deleteData(t.startOffset, t.endOffset - t.startOffset); + + // Nothing is partially selected, so collapse to start point + t.collapse(true); + } + + if (how == DELETE) + return null; + + frag.appendChild(t.dom.doc.createTextNode(sub)); + return frag; + } + + // Copy nodes between the start/end offsets. + n = getSelectedNode(t.startContainer, t.startOffset); + cnt = t.endOffset - t.startOffset; + + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._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; + }, + + _traverseCommonStartContainer : function(endAncestor, how) { + var t = this, frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = indexOf(endAncestor, t.startContainer); + cnt = endIdx - t.startOffset; + + 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 = t._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; + }, + + _traverseCommonEndContainer : function(startAncestor, how) { + var t = this, frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = indexOf(startAncestor, t.endContainer); + ++startIdx; // Because we already traversed it.... + + cnt = t.endOffset - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseCommonAncestors : function(startAncestor, endAncestor, how) { + var t = this, n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = indexOf(startAncestor, commonParent); + endOffset = indexOf(endAncestor, commonParent); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = t._traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseRightBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.endContainer, t.endOffset - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent; + var isFullySelected = next != t.endContainer; + + if (next == root) + return t._traverseNode(next, isFullySelected, false, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, false, how); + + while (parent != null) { + while (next != null) { + prevSibling = next.previousSibling; + clonedChild = t._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 = t._traverseNode(parent, false, false, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseLeftBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.startContainer, t.startOffset); + var isFullySelected = next != t.startContainer, parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return t._traverseNode(next, isFullySelected, true, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, true, how); + + while (parent != null) { + while (next != null) { + nextSibling = next.nextSibling; + clonedChild = t._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 = t._traverseNode(parent, false, true, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseNode : function(n, isFullySelected, isLeft, how) { + var t = this, txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return t._traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t.startOffset; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t.endOffset; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return null; + + newNode = n.cloneNode(false); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return null; + + return n.cloneNode(false); + }, + + _traverseFullySelected : function(n, how) { + var t = this; + + if (how != DELETE) + return how == CLONE ? n.cloneNode(true) : n; + + n.parentNode.removeChild(n); + return null; + } + }); + + ns.Range = Range; +})(tinymce.dom); +(function() { + function Selection(selection) { + var t = this, invisibleChar = '\uFEFF', range, lastIERng; + + 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 1; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return 1; + } + + return 0; + }; + + function getRange() { + var dom = selection.dom, ieRange = selection.getRng(), domRange = dom.createRng(), startPos, endPos, element, sc, ec, collapsed; + + function findIndex(element) { + var nl = element.parentNode.childNodes, i; + + for (i = nl.length - 1; i >= 0; i--) { + if (nl[i] == element) + return i; + } + + return -1; + }; + + function findEndPoint(start) { + var rng = ieRange.duplicate(), parent, i, nl, n, offset = 0, index = 0, pos, tmpRng; + + // Insert marker character + rng.collapse(start); + parent = rng.parentElement(); + rng.pasteHTML(invisibleChar); // Needs to be a pasteHTML instead of .text = since IE has a bug with nodeValue + + // Find marker character + nl = parent.childNodes; + for (i = 0; i < nl.length; i++) { + n = nl[i]; + + // Calculate node index excluding text node fragmentation + if (i > 0 && (n.nodeType !== 3 || nl[i - 1].nodeType !== 3)) + index++; + + // If text node then calculate offset + if (n.nodeType === 3) { + // Look for marker + pos = n.nodeValue.indexOf(invisibleChar); + if (pos !== -1) { + offset += pos; + break; + } + + offset += n.nodeValue.length; + } else + offset = 0; + } + + // Remove marker character + rng.moveStart('character', -1); + rng.text = ''; + + return {index : index, offset : offset, parent : parent}; + }; + + // 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, findIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + // Check collapsed state + collapsed = selection.isCollapsed(); + + // Find start and end pos index and offset + startPos = findEndPoint(true); + endPos = findEndPoint(false); + + // Normalize the elements to avoid fragmented dom + startPos.parent.normalize(); + endPos.parent.normalize(); + + // Set start container and offset + sc = startPos.parent.childNodes[Math.min(startPos.index, startPos.parent.childNodes.length - 1)]; + + if (sc.nodeType != 3) + domRange.setStart(startPos.parent, startPos.index); + else + domRange.setStart(startPos.parent.childNodes[startPos.index], startPos.offset); + + // Set end container and offset + ec = endPos.parent.childNodes[Math.min(endPos.index, endPos.parent.childNodes.length - 1)]; + + if (ec.nodeType != 3) { + if (!collapsed) + endPos.index++; + + domRange.setEnd(endPos.parent, endPos.index); + } else + domRange.setEnd(endPos.parent.childNodes[endPos.index], endPos.offset); + + // If not collapsed then make sure offsets are valid + if (!collapsed) { + sc = domRange.startContainer; + if (sc.nodeType == 1) + domRange.setStart(sc, Math.min(domRange.startOffset, sc.childNodes.length)); + + ec = domRange.endContainer; + if (ec.nodeType == 1) + domRange.setEnd(ec, Math.min(domRange.endOffset, ec.childNodes.length)); + } + + // Restore selection to new range + t.addRange(domRange); + + return domRange; + }; + + this.addRange = function(rng) { + var ieRng, body = selection.dom.doc.body, startPos, endPos, sc, so, ec, eo; + + // Setup some shorter versions + sc = rng.startContainer; + so = rng.startOffset; + ec = rng.endContainer; + eo = rng.endOffset; + ieRng = body.createTextRange(); + + // Find element + sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc; + ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec; + + // 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(); + + return; + } + + function getCharPos(container, offset) { + var nodeVal, rng, pos; + + if (container.nodeType != 3) + return -1; + + nodeVal = container.nodeValue; + rng = body.createTextRange(); + + // Insert marker at offset position + container.nodeValue = nodeVal.substring(0, offset) + invisibleChar + nodeVal.substring(offset); + + // Find char pos of marker and remove it + rng.moveToElementText(container.parentNode); + rng.findText(invisibleChar); + pos = Math.abs(rng.moveStart('character', -0xFFFFF)); + container.nodeValue = nodeVal; + + return pos; + }; + + // Collapsed range + if (rng.collapsed) { + pos = getCharPos(sc, so); + + ieRng = body.createTextRange(); + ieRng.move('character', pos); + ieRng.select(); + + return; + } else { + // If same text container + if (sc == ec && sc.nodeType == 3) { + startPos = getCharPos(sc, so); + + ieRng.move('character', startPos); + ieRng.moveEnd('character', eo - so); + ieRng.select(); + + return; + } + + // Get caret positions + startPos = getCharPos(sc, so); + endPos = getCharPos(ec, eo); + ieRng = body.createTextRange(); + + // Move start of range to start character position or start element + if (startPos == -1) { + ieRng.moveToElementText(sc); + startPos = 0; + } else + ieRng.move('character', startPos); + + // Move end of range to end character position or end element + tmpRng = body.createTextRange(); + + if (endPos == -1) + tmpRng.moveToElementText(ec); + else + tmpRng.move('character', endPos); + + ieRng.setEndPoint('EndToEnd', tmpRng); + ieRng.select(); + + return; + } + }; + + 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; + }; + }; + + // 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; + } + } + + }); + + // Shorten name and setup global instance + 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) { + var each = tinymce.each; + + tinymce.create('tinymce.dom.Element', { + Element : function(id, s) { + var t = this, dom, el; + + s = s || {}; + t.id = id; + t.dom = dom = s.dom || tinymce.DOM; + t.settings = s; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = t.dom.get(t.id); + + each([ + 'getPos', + 'getRect', + 'getParent', + 'add', + 'setStyle', + 'getStyle', + 'setStyles', + 'setAttrib', + 'setAttribs', + 'getAttrib', + 'addClass', + 'removeClass', + 'hasClass', + 'getOuterHTML', + 'setOuterHTML', + 'remove', + 'show', + 'hide', + 'isHidden', + 'setHTML', + 'get' + ], 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; + }; + }); + }, + + on : function(n, f, s) { + return tinymce.dom.Event.add(this.id, n, f, s); + }, + + getXY : function() { + return { + x : parseInt(this.getStyle('left')), + y : parseInt(this.getStyle('top')) + }; + }, + + getSize : function() { + var n = this.dom.get(this.id); + + return { + w : parseInt(this.getStyle('width') || n.clientWidth), + h : parseInt(this.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + this.setStyles({left : x, top : y}); + }, + + moveBy : function(x, y) { + var p = this.getXY(); + + this.moveTo(p.x + x, p.y + y); + }, + + resizeTo : function(w, h) { + this.setStyles({width : w, height : h}); + }, + + resizeBy : function(w, h) { + var s = this.getSize(); + + this.resizeTo(s.w + w, s.h + h); + }, + + update : function(k) { + var t = this, b, dom = t.dom; + + if (tinymce.isIE6 && t.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(t.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.setStyle(b, 'left', t.getStyle('left', 1)); + dom.setStyle(b, 'top', t.getStyle('top', 1)); + dom.setStyle(b, 'width', t.getStyle('width', 1)); + dom.setStyle(b, 'height', t.getStyle('height', 1)); + dom.setStyle(b, 'display', t.getStyle('display', 1)); + dom.setStyle(b, '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 + 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.setEndAfter(c); + t.setRng(r); + + // Delete the marker, and hopefully the caret gets placed in the right location + // Removed this since it seems to remove in FF and simply deleting it + // doesn't seem to affect the caret position in any browser + //d.execCommand('Delete', false, null); + + // 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 (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(1); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.firstChild; + + return e; + } else { + e = r.startContainer; + + if (e.nodeName == 'BODY') + return e.firstChild; + + return t.dom.getParent(e, '*'); + } + }, + + getEnd : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild; + + return e; + } else { + e = r.endContainer; + + if (e.nodeName == 'BODY') + return e.lastChild; + + return t.dom.getParent(e, '*'); + } + }, + + getBookmark : function(si) { + var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv; + sx = vp.x; + sy = vp.y; + + // Simple bookmark fast but not as persistent + if (si) + return {rng : r, scrollX : sx, scrollY : sy}; + + // Handle IE + if (isIE) { + // Control selection + if (r.item) { + e = r.item(0); + + each(t.dom.select(e.nodeName), function(n, i) { + if (e == n) { + sp = i; + return false; + } + }); + + return { + tag : e.nodeName, + index : sp, + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + tr = t.dom.doc.body.createTextRange(); + tr.moveToElementText(ro); + tr.collapse(true); + bp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(true); + sp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(false); + le = Math.abs(tr.move('character', c)) - sp; + + return { + start : sp - bp, + length : le, + scrollX : sx, + scrollY : sy + }; + } + + // Handle W3C + e = t.getNode(); + s = t.getSel(); + + if (!s) + return null; + + // Image selection + if (e && e.nodeName == 'IMG') { + return { + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + + function getPos(r, sn, en) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; + + while ((n = w.nextNode()) != null) { + if (n == sn) + d.start = p; + + if (n == en) { + d.end = p; + return d; + } + + p += trimNl(n.nodeValue || '').length; + } + + return null; + }; + + // Caret or selection + if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) { + e = getPos(ro, s.anchorNode, s.focusNode); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + // Count whitespace before + trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + + return { + start : Math.max(e.start + s.anchorOffset - wb, 0), + end : Math.max(e.end + s.focusOffset - wb, 0), + scrollX : sx, + scrollY : sy, + beg : s.anchorOffset - wb == 0 + }; + } else { + e = getPos(ro, r.startContainer, r.endContainer); + + // Count whitespace before start and end container + //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;}); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + return { + start : Math.max(e.start + r.startOffset - wb, 0), + end : Math.max(e.end + r.endOffset - wa, 0), + scrollX : sx, + scrollY : sy, + beg : r.startOffset - wb == 0 + }; + } + }, + + moveToBookmark : function(b) { + var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv; + + function getPos(r, sp, ep) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb; + + while ((n = w.nextNode()) != null) { + wa = wb = 0; + + nv = n.nodeValue || ''; + //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;}); + //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;}); + + nvl = trimNl(nv).length; + p += nvl; + + if (p >= sp && !d.startNode) { + o = sp - (p - nvl); + + // Fix for odd quirk in FF + if (b.beg && o >= nvl) + continue; + + d.startNode = n; + d.startOffset = o + wb; + } + + if (p >= ep) { + d.endNode = n; + d.endOffset = ep - (p - nvl) + wb; + return d; + } + } + + return null; + }; + + if (!b) + return false; + + t.win.scrollTo(b.scrollX, b.scrollY); + + // Handle explorer + if (isIE) { + // Handle simple + if (r = b.rng) { + try { + r.select(); + } catch (ex) { + // Ignore + } + + return true; + } + + t.win.focus(); + + // Handle control bookmark + if (b.tag) { + r = ro.createControlRange(); + + each(t.dom.select(b.tag), function(n, i) { + if (i == b.index) + r.addElement(n); + }); + } else { + // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs + try { + // Incorrect bookmark + if (b.start < 0) + return true; + + r = s.createRange(); + r.moveToElementText(ro); + r.collapse(true); + r.moveStart('character', b.start); + r.moveEnd('character', b.length); + } catch (ex2) { + return true; + } + } + + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + + return true; + } + + // Handle W3C + if (!s) + return false; + + // Handle simple + if (b.rng) { + s.removeAllRanges(); + s.addRange(b.rng); + } else { + if (is(b.start) && is(b.end)) { + try { + sd = getPos(ro, b.start, b.end); + + if (sd) { + r = t.dom.doc.createRange(); + r.setStart(sd.startNode, sd.startOffset); + r.setEnd(sd.endNode, sd.endOffset); + s.removeAllRanges(); + s.addRange(r); + } + + if (!tinymce.isOpera) + t.win.focus(); + } catch (ex) { + // Ignore + } + } + } + }, + + select : function(n, c) { + var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document; + + function find(n, start) { + var walker, o; + + if (n) { + walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + // Find first/last non empty text node + while (n = walker.nextNode()) { + o = n; + + if (tinymce.trim(n.nodeValue).length != 0) { + if (start) + return n; + else + o = n; + } + } + } + + return o; + }; + + if (isIE) { + try { + b = d.body; + + if (/^(IMG|TABLE)$/.test(n.nodeName)) { + r = b.createControlRange(); + r.addElement(n); + } else { + r = b.createTextRange(); + r.moveToElementText(n); + } + + r.select(); + } catch (ex) { + // Throws illigal agrument in IE some times + } + } else { + if (c) { + fn = find(n, 1) || t.dom.select('br:first', n)[0]; + ln = find(n, 0) || t.dom.select('br:last', n)[0]; + + if (fn && ln) { + r = d.createRange(); + + if (fn.nodeName == 'BR') + r.setStartBefore(fn); + else + r.setStart(fn, 0); + + if (ln.nodeName == 'BR') + r.setEndBefore(ln); + else + r.setEnd(ln, ln.nodeValue.length); + } else + r.selectNode(n); + } else + r.selectNode(n); + + t.setRng(r); + } + + return n; + }, + + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + return !s || r.boundingWidth == 0 || 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 = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange(); + + 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, r = t.getRng(), s = t.getSel(), e; + + if (!isIE) { + // Range maybe lost after the editor is made visible again + if (!r) + return t.dom.getRoot(); + + e = r.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!r.collapsed) { + // If the anchor node is a element instead of a text node then return this element + if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1) + return s.anchorNode.childNodes[s.anchorOffset]; + + if (r.startContainer == r.endContainer) { + if (r.startOffset - r.endOffset < 2) { + if (r.startContainer.hasChildNodes()) + e = r.startContainer.childNodes[r.startOffset]; + } + } + } + + return t.dom.getParent(e, '*'); + } + + return r.item ? r.item(0) : r.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('' + n + '>'); + + if (this.settings.indentation > 0) + this.writeRaw('\n'); + } + }, + + writeFullEndElement : function() { + if (this.tags.length > 0) { + this._writeAttributesEnd(); + this.writeRaw('' + this.tags.pop() + '>'); + + 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', + bool_attrs : /(checked|disabled|readonly|selected|nowrap)/, + valid_elements : '*[*]', + extended_valid_elements : 0, + valid_child_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, + font_size_style_values : 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; + + 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 '' + c + '>'; + + 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 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 ]*>)(.*?)(<\/script>)/g}, + {pattern : /(]*>)(.*?)(<\/noscript>)/g}, + {pattern : /('; + }); + + // Wrap noscript elements + h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { + return ''; + }); + } + + h = h.replace(//g, ''); + + // Process all tags with src, href or style + h = h.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi, function(a, n) { + function handle(m, b, c) { + var u = c; + + // Tag already got a mce_ version + if (a.indexOf('mce_' + b) != -1) + return m; + + if (b == 'style') { + // No mce_style for elements with these since they might get resized by the user + if (t._isRes(c)) + return m; + + if (s.hex_colors) { + u = u.replace(/rgb\([^\)]+\)/g, function(v) { + return t.toHex(v); + }); + } + + if (s.url_converter) { + u = u.replace(/url\([\'\"]?([^\)\'\"]+)\)/g, function(x, c) { + return 'url(' + t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)) + ')'; + }); + } + } else if (b != 'coords' && b != 'shape') { + if (s.url_converter) + u = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n)); + } + + return ' ' + b + '="' + c + '" mce_' + b + '="' + u + '"'; + }; + + a = a.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi, handle); // W3C + a = a.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi, handle); // W3C + + return a.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi, handle); // IE + }); + } + + 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; + + return this.run(e, function(e) { + var n, tp; + + e = t.get(e); + d = d || e.ownerDocument || t.doc; + + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else { + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + } + }); + }, + + decode : function(s) { + var e, n, v; + + // Look for entities to decode + if (/&[^;]+;/.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.nextSibling); + } + + return v || s; + } + + return s; + }, + + encode : function(s) { + return s ? ('' + s).replace(/[<>&\"]/g, function (c, b) { + switch (c) { + case '&': + return '&'; + + case '"': + return '"'; + + case '<': + return '<'; + + case '>': + return '>'; + } + + return c; + }) : s; + }, + + insertAfter : function(n, r) { + var t = this; + + r = t.get(r); + + return this.run(n, function(n) { + var p, ns; + + p = r.parentNode; + ns = r.nextSibling; + + if (ns) + p.insertBefore(n, ns); + else + p.appendChild(n); + + return n; + }); + }, + + isBlock : function(n) { + if (n.nodeType && n.nodeType !== 1) + return false; + + n = n.nodeName || n; + + return /^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.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(o.childNodes, function(c) { + n.appendChild(c.cloneNode(true)); + }); + } + + // Fix IE psuedo leak for elements since replacing elements if fairly common + // Will break parentNode for some unknown reason + if (t.fixPsuedoLeaks && o.nodeType === 1) { + o.parentNode.insertBefore(n, o); + t.remove(o); + return n; + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + 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; + + // 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(/([a-z0-9\:\-_]+)=/gi, function(a, b) { + o.push({specified : 1, nodeName : b}); + }); + + 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); + }, + + 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 sence + // but we don't want that in our code since it serves no purpose + // For example if this is chopped: + // text 1CHOPtext 2 + // would produce: + // text 1CHOPtext 2 + // this function will then trim of empty edges and produce: + // text 1CHOPtext 2 + function trimEdge(n, na) { + n = n[na]; + + if (n && n[na] && n[na].nodeType == 1 && isEmpty(n[na])) + t.remove(n[na]); + }; + + function isEmpty(n) { + n = t.getOuterHTML(n); + n = n.replace(/<(img|hr|table)/gi, '-'); // Keep these convert them to - chars + n = n.replace(/<[^>]+>/g, ''); // Remove all tags + + return n.replace(/[ \t\r\n]+| | /g, '') == ''; + }; + + if (pe && e) { + // Get before chunk + r.setStartBefore(pe); + r.setEndBefore(e); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStartAfter(e); + r.setEndAfter(pe); + aft = r.extractContents(); + + // Insert chunks and remove parent + pa = pe.parentNode; + + // Remove right side edge of the before contents + trimEdge(bef, 'lastChild'); + + if (!isEmpty(bef)) + pa.insertBefore(bef, pe); + + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Remove left site edge of the after contents + trimEdge(aft, 'firstChild'); + + if (!isEmpty(aft)) + pa.insertBefore(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); + }, + + + _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; + } + */ + + }); + + // Setup page DOM + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); +(function(ns) { + // Traverse constants + var EXTRACT = 0, CLONE = 1, DELETE = 2, extend = tinymce.extend; + + function indexOf(child, parent) { + var i, node; + + if (child.parentNode != parent) + return -1; + + for (node = parent.firstChild, i = 0; node != child; node = node.nextSibling) + i++; + + return i; + }; + + function nodeIndex(n) { + var i = 0; + + while (n.previousSibling) { + i++; + n = n.previousSibling; + } + + return i; + }; + + function getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child != null && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child != null) + return child; + + return container; + }; + + // Range constructor + function Range(dom) { + var d = dom.doc; + + extend(this, { + dom : dom, + + // Inital states + startContainer : d, + startOffset : 0, + endContainer : d, + endOffset : 0, + collapsed : true, + commonAncestorContainer : d, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3 + }); + }; + + // Add range methods + extend(Range.prototype, { + setStart : function(n, o) { + this._setEndPoint(true, n, o); + }, + + setEnd : function(n, o) { + this._setEndPoint(false, n, o); + }, + + setStartBefore : function(n) { + this.setStart(n.parentNode, nodeIndex(n)); + }, + + setStartAfter : function(n) { + this.setStart(n.parentNode, nodeIndex(n) + 1); + }, + + setEndBefore : function(n) { + this.setEnd(n.parentNode, nodeIndex(n)); + }, + + setEndAfter : function(n) { + this.setEnd(n.parentNode, nodeIndex(n) + 1); + }, + + collapse : function(ts) { + var t = this; + + if (ts) { + t.endContainer = t.startContainer; + t.endOffset = t.startOffset; + } else { + t.startContainer = t.endContainer; + t.startOffset = t.endOffset; + } + + t.collapsed = true; + }, + + selectNode : function(n) { + this.setStartBefore(n); + this.setEndAfter(n); + }, + + selectNodeContents : function(n) { + this.setStart(n, 0); + this.setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }, + + compareBoundaryPoints : function(h, r) { + var t = this, sc = t.startContainer, so = t.startOffset, ec = t.endContainer, eo = t.endOffset; + + // Check START_TO_START + if (h === 0) + return t._compareBoundaryPoints(sc, so, sc, so); + + // Check START_TO_END + if (h === 1) + return t._compareBoundaryPoints(sc, so, ec, eo); + + // Check END_TO_END + if (h === 2) + return t._compareBoundaryPoints(ec, eo, ec, eo); + + // Check END_TO_START + if (h === 3) + return t._compareBoundaryPoints(ec, eo, sc, so); + }, + + deleteContents : function() { + this._traverse(DELETE); + }, + + extractContents : function() { + return this._traverse(EXTRACT); + }, + + cloneContents : function() { + return this._traverse(CLONE); + }, + + insertNode : function(n) { + var t = this, nn, o; + + // Node is TEXT_NODE or CDATA + if (n.nodeType === 3 || n.nodeType === 4) { + nn = t.startContainer.splitText(t.startOffset); + t.startContainer.parentNode.insertBefore(n, nn); + } else { + // Insert element node + if (t.startContainer.childNodes.length > 0) + o = t.startContainer.childNodes[t.startOffset]; + + t.startContainer.insertBefore(n, o); + } + }, + + surroundContents : function(n) { + var t = this, f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }, + + cloneRange : function() { + var t = this; + + return extend(new Range(t.dom), { + startContainer : t.startContainer, + startOffset : t.startOffset, + endContainer : t.endContainer, + endOffset : t.endOffset, + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }, + +/* + detach : function() { + // Not implemented + }, +*/ + // Internal methods + + _isCollapsed : function() { + return (this.startContainer == this.endContainer && this.startOffset == this.endOffset); + }, + + _compareBoundaryPoints : function (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 + } else if (offsetA < offsetB) { + return -1; // before + } else { + 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 + } else { + 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 + } else { + 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 = this.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; + } + }, + + _setEndPoint : function(st, n, o) { + var t = this, ec, sc; + + if (st) { + t.startContainer = n; + t.startOffset = o; + } else { + t.endContainer = n; + t.endOffset = 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.endContainer; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t.startContainer; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc != ec) { + t.collapse(st); + } else { + // 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 (t._compareBoundaryPoints(t.startContainer, t.startOffset, t.endContainer, t.endOffset) > 0) + t.collapse(st); + } + + t.collapsed = t._isCollapsed(); + t.commonAncestorContainer = t.dom.findCommonAncestor(t.startContainer, t.endContainer); + }, + + // This code is heavily "inspired" by the Apache Xerces implementation. I hope they don't mind. :) + + _traverse : function(how) { + var t = this, c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t.startContainer == t.endContainer) + return t._traverseSameContainer(how); + + for (c = t.endContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.startContainer) + return t._traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t.startContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) { + if (p == t.endContainer) + return t._traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t.startContainer; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t.endContainer; + 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 t._traverseCommonAncestors(startNode, endNode, how); + }, + + _traverseSameContainer : function(how) { + var t = this, frag, s, sub, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t.startOffset == t.endOffset) + return frag; + + // Text node needs special case handling + if (t.startContainer.nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t.startContainer.nodeValue; + sub = s.substring(t.startOffset, t.endOffset); + + // set the original text node to its new value + if (how != CLONE) { + t.startContainer.deleteData(t.startOffset, t.endOffset - t.startOffset); + + // Nothing is partially selected, so collapse to start point + t.collapse(true); + } + + if (how == DELETE) + return null; + + frag.appendChild(t.dom.doc.createTextNode(sub)); + return frag; + } + + // Copy nodes between the start/end offsets. + n = getSelectedNode(t.startContainer, t.startOffset); + cnt = t.endOffset - t.startOffset; + + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._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; + }, + + _traverseCommonStartContainer : function(endAncestor, how) { + var t = this, frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = indexOf(endAncestor, t.startContainer); + cnt = endIdx - t.startOffset; + + 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 = t._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; + }, + + _traverseCommonEndContainer : function(startAncestor, how) { + var t = this, frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = indexOf(startAncestor, t.endContainer); + ++startIdx; // Because we already traversed it.... + + cnt = t.endOffset - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = t._traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseCommonAncestors : function(startAncestor, endAncestor, how) { + var t = this, n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = t.dom.doc.createDocumentFragment(); + + n = t._traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = indexOf(startAncestor, commonParent); + endOffset = indexOf(endAncestor, commonParent); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = t._traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = t._traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(true); + } + + return frag; + }, + + _traverseRightBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.endContainer, t.endOffset - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent; + var isFullySelected = next != t.endContainer; + + if (next == root) + return t._traverseNode(next, isFullySelected, false, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, false, how); + + while (parent != null) { + while (next != null) { + prevSibling = next.previousSibling; + clonedChild = t._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 = t._traverseNode(parent, false, false, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseLeftBoundary : function(root, how) { + var t = this, next = getSelectedNode(t.startContainer, t.startOffset); + var isFullySelected = next != t.startContainer, parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return t._traverseNode(next, isFullySelected, true, how); + + parent = next.parentNode; + clonedParent = t._traverseNode(parent, false, true, how); + + while (parent != null) { + while (next != null) { + nextSibling = next.nextSibling; + clonedChild = t._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 = t._traverseNode(parent, false, true, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + + // should never occur + return null; + }, + + _traverseNode : function(n, isFullySelected, isLeft, how) { + var t = this, txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return t._traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t.startOffset; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t.endOffset; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return null; + + newNode = n.cloneNode(false); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return null; + + return n.cloneNode(false); + }, + + _traverseFullySelected : function(n, how) { + var t = this; + + if (how != DELETE) + return how == CLONE ? n.cloneNode(true) : n; + + n.parentNode.removeChild(n); + return null; + } + }); + + ns.Range = Range; +})(tinymce.dom); +(function() { + function Selection(selection) { + var t = this, invisibleChar = '\uFEFF', range, lastIERng; + + 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 1; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return 1; + } + + return 0; + }; + + function getRange() { + var dom = selection.dom, ieRange = selection.getRng(), domRange = dom.createRng(), startPos, endPos, element, sc, ec, collapsed; + + function findIndex(element) { + var nl = element.parentNode.childNodes, i; + + for (i = nl.length - 1; i >= 0; i--) { + if (nl[i] == element) + return i; + } + + return -1; + }; + + function findEndPoint(start) { + var rng = ieRange.duplicate(), parent, i, nl, n, offset = 0, index = 0, pos, tmpRng; + + // Insert marker character + rng.collapse(start); + parent = rng.parentElement(); + rng.pasteHTML(invisibleChar); // Needs to be a pasteHTML instead of .text = since IE has a bug with nodeValue + + // Find marker character + nl = parent.childNodes; + for (i = 0; i < nl.length; i++) { + n = nl[i]; + + // Calculate node index excluding text node fragmentation + if (i > 0 && (n.nodeType !== 3 || nl[i - 1].nodeType !== 3)) + index++; + + // If text node then calculate offset + if (n.nodeType === 3) { + // Look for marker + pos = n.nodeValue.indexOf(invisibleChar); + if (pos !== -1) { + offset += pos; + break; + } + + offset += n.nodeValue.length; + } else + offset = 0; + } + + // Remove marker character + rng.moveStart('character', -1); + rng.text = ''; + + return {index : index, offset : offset, parent : parent}; + }; + + // 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, findIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + // Check collapsed state + collapsed = selection.isCollapsed(); + + // Find start and end pos index and offset + startPos = findEndPoint(true); + endPos = findEndPoint(false); + + // Normalize the elements to avoid fragmented dom + startPos.parent.normalize(); + endPos.parent.normalize(); + + // Set start container and offset + sc = startPos.parent.childNodes[Math.min(startPos.index, startPos.parent.childNodes.length - 1)]; + + if (sc.nodeType != 3) + domRange.setStart(startPos.parent, startPos.index); + else + domRange.setStart(startPos.parent.childNodes[startPos.index], startPos.offset); + + // Set end container and offset + ec = endPos.parent.childNodes[Math.min(endPos.index, endPos.parent.childNodes.length - 1)]; + + if (ec.nodeType != 3) { + if (!collapsed) + endPos.index++; + + domRange.setEnd(endPos.parent, endPos.index); + } else + domRange.setEnd(endPos.parent.childNodes[endPos.index], endPos.offset); + + // If not collapsed then make sure offsets are valid + if (!collapsed) { + sc = domRange.startContainer; + if (sc.nodeType == 1) + domRange.setStart(sc, Math.min(domRange.startOffset, sc.childNodes.length)); + + ec = domRange.endContainer; + if (ec.nodeType == 1) + domRange.setEnd(ec, Math.min(domRange.endOffset, ec.childNodes.length)); + } + + // Restore selection to new range + t.addRange(domRange); + + return domRange; + }; + + this.addRange = function(rng) { + var ieRng, body = selection.dom.doc.body, startPos, endPos, sc, so, ec, eo; + + // Setup some shorter versions + sc = rng.startContainer; + so = rng.startOffset; + ec = rng.endContainer; + eo = rng.endOffset; + ieRng = body.createTextRange(); + + // Find element + sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc; + ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec; + + // 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(); + + return; + } + + function getCharPos(container, offset) { + var nodeVal, rng, pos; + + if (container.nodeType != 3) + return -1; + + nodeVal = container.nodeValue; + rng = body.createTextRange(); + + // Insert marker at offset position + container.nodeValue = nodeVal.substring(0, offset) + invisibleChar + nodeVal.substring(offset); + + // Find char pos of marker and remove it + rng.moveToElementText(container.parentNode); + rng.findText(invisibleChar); + pos = Math.abs(rng.moveStart('character', -0xFFFFF)); + container.nodeValue = nodeVal; + + return pos; + }; + + // Collapsed range + if (rng.collapsed) { + pos = getCharPos(sc, so); + + ieRng = body.createTextRange(); + ieRng.move('character', pos); + ieRng.select(); + + return; + } else { + // If same text container + if (sc == ec && sc.nodeType == 3) { + startPos = getCharPos(sc, so); + + ieRng.move('character', startPos); + ieRng.moveEnd('character', eo - so); + ieRng.select(); + + return; + } + + // Get caret positions + startPos = getCharPos(sc, so); + endPos = getCharPos(ec, eo); + ieRng = body.createTextRange(); + + // Move start of range to start character position or start element + if (startPos == -1) { + ieRng.moveToElementText(sc); + startPos = 0; + } else + ieRng.move('character', startPos); + + // Move end of range to end character position or end element + tmpRng = body.createTextRange(); + + if (endPos == -1) + tmpRng.moveToElementText(ec); + else + tmpRng.move('character', endPos); + + ieRng.setEndPoint('EndToEnd', tmpRng); + ieRng.select(); + + return; + } + }; + + 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; + }; + }; + + // 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; + } + } + + }); + + // Shorten name and setup global instance + 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) { + var each = tinymce.each; + + tinymce.create('tinymce.dom.Element', { + Element : function(id, s) { + var t = this, dom, el; + + s = s || {}; + t.id = id; + t.dom = dom = s.dom || tinymce.DOM; + t.settings = s; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = t.dom.get(t.id); + + each([ + 'getPos', + 'getRect', + 'getParent', + 'add', + 'setStyle', + 'getStyle', + 'setStyles', + 'setAttrib', + 'setAttribs', + 'getAttrib', + 'addClass', + 'removeClass', + 'hasClass', + 'getOuterHTML', + 'setOuterHTML', + 'remove', + 'show', + 'hide', + 'isHidden', + 'setHTML', + 'get' + ], 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; + }; + }); + }, + + on : function(n, f, s) { + return tinymce.dom.Event.add(this.id, n, f, s); + }, + + getXY : function() { + return { + x : parseInt(this.getStyle('left')), + y : parseInt(this.getStyle('top')) + }; + }, + + getSize : function() { + var n = this.dom.get(this.id); + + return { + w : parseInt(this.getStyle('width') || n.clientWidth), + h : parseInt(this.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + this.setStyles({left : x, top : y}); + }, + + moveBy : function(x, y) { + var p = this.getXY(); + + this.moveTo(p.x + x, p.y + y); + }, + + resizeTo : function(w, h) { + this.setStyles({width : w, height : h}); + }, + + resizeBy : function(w, h) { + var s = this.getSize(); + + this.resizeTo(s.w + w, s.h + h); + }, + + update : function(k) { + var t = this, b, dom = t.dom; + + if (tinymce.isIE6 && t.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(t.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.setStyle(b, 'left', t.getStyle('left', 1)); + dom.setStyle(b, 'top', t.getStyle('top', 1)); + dom.setStyle(b, 'width', t.getStyle('width', 1)); + dom.setStyle(b, 'height', t.getStyle('height', 1)); + dom.setStyle(b, 'display', t.getStyle('display', 1)); + dom.setStyle(b, '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 + 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.setEndAfter(c); + t.setRng(r); + + // Delete the marker, and hopefully the caret gets placed in the right location + // Removed this since it seems to remove in FF and simply deleting it + // doesn't seem to affect the caret position in any browser + //d.execCommand('Delete', false, null); + + // 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 (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(1); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.firstChild; + + return e; + } else { + e = r.startContainer; + + if (e.nodeName == 'BODY') + return e.firstChild; + + return t.dom.getParent(e, '*'); + } + }, + + getEnd : function() { + var t = this, r = t.getRng(), e; + + if (isIE) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild; + + return e; + } else { + e = r.endContainer; + + if (e.nodeName == 'BODY') + return e.lastChild; + + return t.dom.getParent(e, '*'); + } + }, + + getBookmark : function(si) { + var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv; + sx = vp.x; + sy = vp.y; + + // Simple bookmark fast but not as persistent + if (si) + return {rng : r, scrollX : sx, scrollY : sy}; + + // Handle IE + if (isIE) { + // Control selection + if (r.item) { + e = r.item(0); + + each(t.dom.select(e.nodeName), function(n, i) { + if (e == n) { + sp = i; + return false; + } + }); + + return { + tag : e.nodeName, + index : sp, + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + tr = t.dom.doc.body.createTextRange(); + tr.moveToElementText(ro); + tr.collapse(true); + bp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(true); + sp = Math.abs(tr.move('character', c)); + + tr = r.duplicate(); + tr.collapse(false); + le = Math.abs(tr.move('character', c)) - sp; + + return { + start : sp - bp, + length : le, + scrollX : sx, + scrollY : sy + }; + } + + // Handle W3C + e = t.getNode(); + s = t.getSel(); + + if (!s) + return null; + + // Image selection + if (e && e.nodeName == 'IMG') { + return { + scrollX : sx, + scrollY : sy + }; + } + + // Text selection + + function getPos(r, sn, en) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; + + while ((n = w.nextNode()) != null) { + if (n == sn) + d.start = p; + + if (n == en) { + d.end = p; + return d; + } + + p += trimNl(n.nodeValue || '').length; + } + + return null; + }; + + // Caret or selection + if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) { + e = getPos(ro, s.anchorNode, s.focusNode); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + // Count whitespace before + trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + + return { + start : Math.max(e.start + s.anchorOffset - wb, 0), + end : Math.max(e.end + s.focusOffset - wb, 0), + scrollX : sx, + scrollY : sy, + beg : s.anchorOffset - wb == 0 + }; + } else { + e = getPos(ro, r.startContainer, r.endContainer); + + // Count whitespace before start and end container + //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;}); + + if (!e) + return {scrollX : sx, scrollY : sy}; + + return { + start : Math.max(e.start + r.startOffset - wb, 0), + end : Math.max(e.end + r.endOffset - wa, 0), + scrollX : sx, + scrollY : sy, + beg : r.startOffset - wb == 0 + }; + } + }, + + moveToBookmark : function(b) { + var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv; + + function getPos(r, sp, ep) { + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb; + + while ((n = w.nextNode()) != null) { + wa = wb = 0; + + nv = n.nodeValue || ''; + //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;}); + //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;}); + + nvl = trimNl(nv).length; + p += nvl; + + if (p >= sp && !d.startNode) { + o = sp - (p - nvl); + + // Fix for odd quirk in FF + if (b.beg && o >= nvl) + continue; + + d.startNode = n; + d.startOffset = o + wb; + } + + if (p >= ep) { + d.endNode = n; + d.endOffset = ep - (p - nvl) + wb; + return d; + } + } + + return null; + }; + + if (!b) + return false; + + t.win.scrollTo(b.scrollX, b.scrollY); + + // Handle explorer + if (isIE) { + // Handle simple + if (r = b.rng) { + try { + r.select(); + } catch (ex) { + // Ignore + } + + return true; + } + + t.win.focus(); + + // Handle control bookmark + if (b.tag) { + r = ro.createControlRange(); + + each(t.dom.select(b.tag), function(n, i) { + if (i == b.index) + r.addElement(n); + }); + } else { + // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs + try { + // Incorrect bookmark + if (b.start < 0) + return true; + + r = s.createRange(); + r.moveToElementText(ro); + r.collapse(true); + r.moveStart('character', b.start); + r.moveEnd('character', b.length); + } catch (ex2) { + return true; + } + } + + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + + return true; + } + + // Handle W3C + if (!s) + return false; + + // Handle simple + if (b.rng) { + s.removeAllRanges(); + s.addRange(b.rng); + } else { + if (is(b.start) && is(b.end)) { + try { + sd = getPos(ro, b.start, b.end); + + if (sd) { + r = t.dom.doc.createRange(); + r.setStart(sd.startNode, sd.startOffset); + r.setEnd(sd.endNode, sd.endOffset); + s.removeAllRanges(); + s.addRange(r); + } + + if (!tinymce.isOpera) + t.win.focus(); + } catch (ex) { + // Ignore + } + } + } + }, + + select : function(n, c) { + var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document; + + function find(n, start) { + var walker, o; + + if (n) { + walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + // Find first/last non empty text node + while (n = walker.nextNode()) { + o = n; + + if (tinymce.trim(n.nodeValue).length != 0) { + if (start) + return n; + else + o = n; + } + } + } + + return o; + }; + + if (isIE) { + try { + b = d.body; + + if (/^(IMG|TABLE)$/.test(n.nodeName)) { + r = b.createControlRange(); + r.addElement(n); + } else { + r = b.createTextRange(); + r.moveToElementText(n); + } + + r.select(); + } catch (ex) { + // Throws illigal agrument in IE some times + } + } else { + if (c) { + fn = find(n, 1) || t.dom.select('br:first', n)[0]; + ln = find(n, 0) || t.dom.select('br:last', n)[0]; + + if (fn && ln) { + r = d.createRange(); + + if (fn.nodeName == 'BR') + r.setStartBefore(fn); + else + r.setStart(fn, 0); + + if (ln.nodeName == 'BR') + r.setEndBefore(ln); + else + r.setEnd(ln, ln.nodeValue.length); + } else + r.selectNode(n); + } else + r.selectNode(n); + + t.setRng(r); + } + + return n; + }, + + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + return !s || r.boundingWidth == 0 || 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 = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange(); + + 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, r = t.getRng(), s = t.getSel(), e; + + if (!isIE) { + // Range maybe lost after the editor is made visible again + if (!r) + return t.dom.getRoot(); + + e = r.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!r.collapsed) { + // If the anchor node is a element instead of a text node then return this element + if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1) + return s.anchorNode.childNodes[s.anchorOffset]; + + if (r.startContainer == r.endContainer) { + if (r.startOffset - r.endOffset < 2) { + if (r.startContainer.hasChildNodes()) + e = r.startContainer.childNodes[r.startOffset]; + } + } + } + + return t.dom.getParent(e, '*'); + } + + return r.item ? r.item(0) : r.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('' + n + '>'); + + if (this.settings.indentation > 0) + this.writeRaw('\n'); + } + }, + + writeFullEndElement : function() { + if (this.tags.length > 0) { + this._writeAttributesEnd(); + this.writeRaw('' + this.tags.pop() + '>'); + + 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', + bool_attrs : /(checked|disabled|readonly|selected|nowrap)/, + valid_elements : '*[*]', + extended_valid_elements : 0, + valid_child_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, + font_size_style_values : 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; + + 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 '' + c + '>'; + + 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 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 ]*>)(.*?)(<\/script>)/g}, + {pattern : /(]*>)(.*?)(<\/noscript>)/g}, + {pattern : /(
x[yz---12]3
<\/p>|
]+)><\/p>|
]+)>|
/g, '
text 1CHOPtext 2
text 1
text 2