Skip to content
This repository
Browse code

2.0.0.0-dev43

* Implemented functional limitation that restricts max number of catalog products in the system
* Implemented cache backend library model for MongoDB
* Converted some more grids in backend from PHP implementation to declarations in layout
* Removed `app/etc/local.xml.additional` sample file, moved detailed description of possible configuration options to documentation
* Refactored `Mage_Core_Model_EntryPointAbstract` to emphasize method `processRequest()` as abstract
* Moved declaration of functional limitations to the nodes `limitations/store` and `limitations/admin_account`
* Bug fixes:
  * Fixed JavaScript and markup issues on product editing page in backend that caused erroneous sending of AJAX-queries and not rendering validation messages
  * Fixed issues of application initialization in cases when `var` directory doesn't have writable permissions. Writable directories are validated at an early stage of initialization
  * Fixed array sorting issues in test `Magento_Filesystem_Adapter_LocalTest::testGetNestedKeys()` that caused occasional failures
  • Loading branch information...
commit 5b266f6edc41802deae87bf7c504034e9788ba95 1 parent b9005af
mage2-team authored

Showing 79 changed files with 2,513 additions and 647 deletions. Show diff stats Hide diff stats

  1. +13 0 CHANGELOG.markdown
  2. +1 51 app/Mage.php
  3. +11 7 app/code/core/Mage/Adminhtml/Block/Catalog/Product.php
  4. +35 15 app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit.php
  5. +0 13 app/code/core/Mage/Adminhtml/Block/System/Cache/Form.php
  6. +11 1 app/code/core/Mage/Adminhtml/controllers/Catalog/ProductController.php
  7. +2 15 app/code/core/Mage/Adminhtml/view/adminhtml/admin/page.phtml
  8. +128 17 app/code/core/Mage/Backend/Block/Widget/Grid/Column/Filter/Price.php
  9. +116 0 app/code/core/Mage/Backend/Block/Widget/Grid/Massaction/Additional.php
  10. +19 3 app/code/core/Mage/Catalog/Model/Product.php
  11. +93 0 app/code/core/Mage/Catalog/Model/Product/Limitation.php
  12. +14 0 app/code/core/Mage/Catalog/Model/Resource/Product.php
  13. +1 1  app/code/core/Mage/Core/Controller/Varien/Front.php
  14. +0 18 app/code/core/Mage/Core/Helper/Data.php
  15. +2 2 app/code/core/Mage/Core/Model/Abstract.php
  16. +5 0 app/code/core/Mage/Core/Model/App.php
  17. +1 17 app/code/core/Mage/Core/Model/Cache.php
  18. +51 0 app/code/core/Mage/Core/Model/Config.php
  19. +14 6 app/code/core/Mage/Core/Model/Design.php
  20. +0 225 app/code/core/Mage/Core/Model/Design/Package/Proxy.php
  21. +60 18 app/code/core/Mage/Core/Model/Dir.php
  22. +1 1  app/code/core/Mage/Core/Model/EntryPoint/Cron.php
  23. +8 1 app/code/core/Mage/Core/Model/EntryPoint/Http.php
  24. +1 1  app/code/core/Mage/Core/Model/EntryPoint/Media.php
  25. +1 11 app/code/core/Mage/Core/Model/EntryPointAbstract.php
  26. +1 1  app/code/core/Mage/Core/Model/Store/Limitation.php
  27. +1 1  app/code/core/Mage/Core/Model/Store/Storage/Db.php
  28. +1 1  app/code/core/Mage/Index/Model/EntryPoint/Indexer.php
  29. +1 1  app/code/core/Mage/Index/Model/EntryPoint/Shell.php
  30. +1 1  app/code/core/Mage/Install/Model/EntryPoint/Console.php
  31. +1 1  app/code/core/Mage/Log/Model/EntryPoint/Shell.php
  32. +1 1  app/code/core/Mage/User/Model/Resource/User.php
  33. +5 0 app/etc/config.xml
  34. +0 80 app/etc/local.xml.additional
  35. +1 1  app/etc/local.xml.template
  36. +34 4 dev/tests/integration/framework/Magento/Test/Cookie.php
  37. +1 1  dev/tests/integration/framework/tests/unit/testsuite/Magento/Test/CookieTest.php
  38. +1 1  dev/tests/integration/framework/tests/unit/testsuite/Magento/Test/EntityTest.php
  39. +3 0  dev/tests/integration/phpunit.xml.dist
  40. +105 0 dev/tests/integration/testsuite/Mage/Adminhtml/controllers/Catalog/ProductControllerTest.php
  41. +1 1  dev/tests/integration/testsuite/Mage/Adminhtml/controllers/System/StoreControllerTest.php
  42. +3 2 dev/tests/integration/testsuite/Mage/Backend/Block/Widget/GridTest.php
  43. +33 13 dev/tests/integration/testsuite/Mage/Catalog/Model/Product/Api/SimpleTest.php
  44. +23 0 dev/tests/integration/testsuite/Mage/Catalog/Model/ProductTest.php
  45. +0 1  dev/tests/integration/testsuite/Mage/Core/Helper/DataTest.php
  46. +34 36 dev/tests/integration/testsuite/Mage/Core/Model/AppTest.php
  47. +6 3 dev/tests/integration/testsuite/Mage/Core/Model/Design/FallbackTest.php
  48. +170 0 dev/tests/integration/testsuite/Mage/Core/Model/DirFilesystemTest.php
  49. +1 1  dev/tests/integration/testsuite/Mage/Core/Model/StoreTest.php
  50. +41 0 dev/tests/integration/testsuite/Mage/Core/Model/_files/App/custom_cache_local.xml
  51. +43 0 dev/tests/integration/testsuite/Mage/Core/Model/_files/Config/custom_cache_local.xml
  52. +4 1 dev/tests/integration/testsuite/Mage/Install/Model/Installer/ConfigTest.php
  53. +3 2 dev/tests/integration/testsuite/Mage/Install/Model/InstallerTest.php
  54. +0 5 dev/tests/integration/testsuite/Mage/Install/controllers/WizardControllerTest.php
  55. +2 2 dev/tests/integration/testsuite/Mage/User/Model/Resource/UserTest.php
  56. +2 2 dev/tests/integration/testsuite/Mage/User/controllers/Adminhtml/UserControllerTest.php
  57. +290 0 dev/tests/integration/testsuite/Magento/Cache/Backend/MongoDbTest.php
  58. +4 3 dev/tests/integration/testsuite/Magento/Filesystem/Adapter/LocalTest.php
  59. +1 0  dev/tests/static/testsuite/Legacy/ConfigTest.php
  60. +1 0  dev/tests/static/testsuite/Legacy/_files/obsolete_constants.php
  61. +1 0  dev/tests/static/testsuite/Legacy/_files/obsolete_methods.php
  62. +1 0  dev/tests/static/testsuite/Php/_files/whitelist/common.txt
  63. +71 0 dev/tests/unit/framework/Magento/Test/Helper/FileSystem.php
  64. +40 0 dev/tests/unit/framework/tests/unit/testsuite/Magento/Test/Helper/FileSystemTest.php
  65. +63 0 dev/tests/unit/testsuite/Mage/Catalog/Model/Product/LimitationTest.php
  66. +40 39 dev/tests/unit/testsuite/Mage/Core/Model/DirTest.php
  67. +2 6 dev/tests/unit/testsuite/Mage/Core/Model/LoggerTest.php
  68. +5 5 dev/tests/unit/testsuite/Mage/Core/Model/Store/LimitationTest.php
  69. +3 2 dev/tests/unit/testsuite/Mage/Install/Model/Installer/ConfigTest.php
  70. +2 3 dev/tests/unit/testsuite/Mage/Theme/Block/Adminhtml/System/Design/Theme/Tab/CssTest.php
  71. +424 0 dev/tests/unit/testsuite/Magento/Cache/Backend/MongoDbTest.php
  72. +3 0  index.php
  73. +34 0 lib/Magento/BootstrapException.php
  74. +410 0 lib/Magento/Cache/Backend/MongoDb.php
  75. +2 1  pub/cron.php
  76. +2 0  pub/index.php
  77. +1 1  pub/lib/mage/backend/bootstrap.js
  78. +1 0  pub/lib/mage/backend/floating-header.js
  79. +1 1  pub/lib/mage/backend/validation.js
13 CHANGELOG.markdown
Source Rendered
... ... @@ -1,3 +1,16 @@
  1 +2.0.0.0-dev43
  2 +=============
  3 +* Implemented functional limitation that restricts max number of catalog products in the system
  4 +* Implemented cache backend library model for MongoDB
  5 +* Converted some more grids in backend from PHP implementation to declarations in layout
  6 +* Removed `app/etc/local.xml.additional` sample file, moved detailed description of possible configuration options to documentation
  7 +* Refactored `Mage_Core_Model_EntryPointAbstract` to emphasize method `processRequest()` as abstract
  8 +* Moved declaration of functional limitations to the nodes `limitations/store` and `limitations/admin_account`
  9 +* Bug fixes:
  10 + * Fixed JavaScript and markup issues on product editing page in backend that caused erroneous sending of AJAX-queries and not rendering validation messages
  11 + * Fixed issues of application initialization in cases when `var` directory doesn't have writable permissions. Writable directories are validated at an early stage of initialization
  12 + * Fixed array sorting issues in test `Magento_Filesystem_Adapter_LocalTest::testGetNestedKeys()` that caused occasional failures
  13 +
1 14 2.0.0.0-dev42
2 15 =============
3 16 * Application initialization improvements:
52 app/Mage.php
@@ -220,7 +220,7 @@ public static function getVersionInfo()
220 220 'revision' => '0',
221 221 'patch' => '0',
222 222 'stability' => 'dev',
223   - 'number' => '42',
  223 + 'number' => '43',
224 224 );
225 225 }
226 226
@@ -797,56 +797,6 @@ public static function printException(Exception $e, $extra = '')
797 797 }
798 798
799 799 /**
800   - * Define system folder directory url by virtue of running script directory name
801   - * Try to find requested folder by shifting to domain root directory
802   - *
803   - * @param string $folder
804   - * @param boolean $exitIfNot
805   - * @return string
806   - */
807   - public static function getScriptSystemUrl($folder, $exitIfNot = false)
808   - {
809   - $runDirUrl = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/');
810   - $runDir = rtrim(dirname($_SERVER['SCRIPT_FILENAME']), DS);
811   -
812   - $baseUrl = null;
813   - if (is_dir($runDir.'/'.$folder)) {
814   - $baseUrl = str_replace(DS, '/', $runDirUrl);
815   - } else {
816   - $runDirUrlArray = explode('/', $runDirUrl);
817   - $runDirArray = explode('/', $runDir);
818   - $count = count($runDirArray);
819   -
820   - for ($i=0; $i < $count; $i++) {
821   - array_pop($runDirUrlArray);
822   - array_pop($runDirArray);
823   - $_runDir = implode('/', $runDirArray);
824   - if (!empty($_runDir)) {
825   - $_runDir .= '/';
826   - }
827   -
828   - if (is_dir($_runDir.$folder)) {
829   - $_runDirUrl = implode('/', $runDirUrlArray);
830   - $baseUrl = str_replace(DS, '/', $_runDirUrl);
831   - break;
832   - }
833   - }
834   - }
835   -
836   - if (is_null($baseUrl)) {
837   - $errorMessage = "Unable detect system directory: $folder";
838   - if ($exitIfNot) {
839   - // exit because of infinity loop
840   - exit($errorMessage);
841   - } else {
842   - self::printException(new Exception(), $errorMessage);
843   - }
844   - }
845   -
846   - return $baseUrl;
847   - }
848   -
849   - /**
850 800 * Set is downloader flag
851 801 *
852 802 * @param bool $flag
18 app/code/core/Mage/Adminhtml/Block/Catalog/Product.php
@@ -42,13 +42,17 @@ class Mage_Adminhtml_Block_Catalog_Product extends Mage_Adminhtml_Block_Widget_C
42 42 */
43 43 protected function _prepareLayout()
44 44 {
45   - $this->_addButton('add_new', array(
46   - 'id' => 'add_new_product',
47   - 'label' => Mage::helper('Mage_Catalog_Helper_Data')->__('Add Product'),
48   - 'class' => 'btn-add',
49   - 'class_name' => 'Mage_Backend_Block_Widget_Button_Split',
50   - 'options' => $this->_getAddProductButtonOptions()
51   - ));
  45 + /** @var $limitation Mage_Catalog_Model_Product_Limitation */
  46 + $limitation = Mage::getObjectManager()->get('Mage_Catalog_Model_Product_Limitation');
  47 + if (!$limitation->isCreateRestricted()) {
  48 + $this->_addButton('add_new', array(
  49 + 'id' => 'add_new_product',
  50 + 'label' => Mage::helper('Mage_Catalog_Helper_Data')->__('Add Product'),
  51 + 'class' => 'btn-add',
  52 + 'class_name' => 'Mage_Backend_Block_Widget_Button_Split',
  53 + 'options' => $this->_getAddProductButtonOptions()
  54 + ));
  55 + }
52 56
53 57 $this->setChild(
54 58 'grid',
50 app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit.php
@@ -304,27 +304,36 @@ protected function _getSaveSplitButtonOptions()
304 304 'default' => true,
305 305 );
306 306 }
307   - $options[] = array(
308   - 'id' => 'new-button',
309   - 'label' => Mage::helper('Mage_Catalog_Helper_Data')->__('Save & New'),
310   - 'data_attribute' => array(
311   - 'mage-init' => array(
312   - 'button' => array('event' => 'saveAndNew', 'target' => '#product-edit-form'),
313   - ),
314   - ),
315   - );
316   - if (!$this->getRequest()->getParam('popup') && $this->getProduct()->isDuplicable()) {
  307 +
  308 + /** @var $limitation Mage_Catalog_Model_Product_Limitation */
  309 + $limitation = Mage::getObjectManager()->get('Mage_Catalog_Model_Product_Limitation');
  310 + if ($this->_isProductNew()) {
  311 + $showAddNewButtons = !$limitation->isCreateRestricted(2);
  312 + } else {
  313 + $showAddNewButtons = !$limitation->isCreateRestricted();
  314 + }
  315 + if ($showAddNewButtons) {
317 316 $options[] = array(
318   - 'id' => 'duplicate-button',
319   - 'label' => Mage::helper('Mage_Catalog_Helper_Data')->__('Save & Duplicate'),
  317 + 'id' => 'new-button',
  318 + 'label' => Mage::helper('Mage_Catalog_Helper_Data')->__('Save & New'),
320 319 'data_attribute' => array(
321 320 'mage-init' => array(
322   - 'button' => array('event' => '', 'target' => '#product-edit-form'),
  321 + 'button' => array('event' => 'saveAndNew', 'target' => '#product-edit-form'),
323 322 ),
324 323 ),
325   - 'onclick' => $this->getRequest()->getActionName() == 'new' ? ''
326   - : 'setLocation(\'' . $this->getDuplicateUrl() . '\')',
327 324 );
  325 + if (!$this->getRequest()->getParam('popup') && $this->getProduct()->isDuplicable()) {
  326 + $options[] = array(
  327 + 'id' => 'duplicate-button',
  328 + 'label' => Mage::helper('Mage_Catalog_Helper_Data')->__('Save & Duplicate'),
  329 + 'data_attribute' => array(
  330 + 'mage-init' => array(
  331 + 'button' => array('event' => '', 'target' => '#product-edit-form'),
  332 + ),
  333 + ),
  334 + 'onclick' => $this->_isProductNew() ? '' : 'setLocation(\'' . $this->getDuplicateUrl() . '\')',
  335 + );
  336 + }
328 337 }
329 338 $options[] = array(
330 339 'id' => 'close-button',
@@ -337,4 +346,15 @@ protected function _getSaveSplitButtonOptions()
337 346 );
338 347 return $options;
339 348 }
  349 +
  350 + /**
  351 + * Check whether new product is being created
  352 + *
  353 + * @return bool
  354 + */
  355 + protected function _isProductNew()
  356 + {
  357 + $product = $this->getProduct();
  358 + return !$product || !$product->getId();
  359 + }
340 360 }
13 app/code/core/Mage/Adminhtml/Block/System/Cache/Form.php
@@ -69,19 +69,6 @@ public function initForm()
69 69 ));
70 70 }
71 71
72   - $fieldset = $form->addFieldset('beta_cache_enable', array(
73   - 'legend' => Mage::helper('Mage_Adminhtml_Helper_Data')->__('Cache Control (beta)')
74   - ));
75   -
76   - foreach (Mage::helper('Mage_Core_Helper_Data')->getCacheBetaTypes() as $type=>$label) {
77   - $fieldset->addField('beta_enable_'.$type, 'checkbox', array(
78   - 'name'=>'beta['.$type.']',
79   - 'label'=>Mage::helper('Mage_Adminhtml_Helper_Data')->__($label),
80   - 'value'=>1,
81   - 'checked'=>(int)Mage::app()->useCache($type),
82   - ));
83   - }
84   -
85 72 $this->setForm($form);
86 73
87 74 return $this;
12 app/code/core/Mage/Adminhtml/controllers/Catalog/ProductController.php
@@ -185,7 +185,11 @@ public function indexAction()
185 185 {
186 186 $this->_title($this->__('Catalog'))
187 187 ->_title($this->__('Manage Products'));
188   -
  188 + /** @var $limitation Mage_Catalog_Model_Product_Limitation */
  189 + $limitation = Mage::getObjectManager()->get('Mage_Catalog_Model_Product_Limitation');
  190 + if ($limitation->isCreateRestricted()) {
  191 + $this->_getSession()->addNotice($limitation->getCreateRestrictedMessage());
  192 + }
189 193 $this->loadLayout();
190 194 $this->renderLayout();
191 195 }
@@ -243,6 +247,12 @@ public function newAction()
243 247 */
244 248 public function editAction()
245 249 {
  250 + /** @var $limitation Mage_Catalog_Model_Product_Limitation */
  251 + $limitation = Mage::getObjectManager()->get('Mage_Catalog_Model_Product_Limitation');
  252 + if ($limitation->isCreateRestricted()) {
  253 + $this->_getSession()->addNotice($limitation->getCreateRestrictedMessage());
  254 + }
  255 +
246 256 $productId = (int) $this->getRequest()->getParam('id');
247 257 $product = $this->_initProduct();
248 258
17 app/code/core/Mage/Adminhtml/view/adminhtml/admin/page.phtml
@@ -24,20 +24,7 @@
24 24 * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
25 25 */
26 26 ?>
27   -<?php /*{
28   - "label":"Root page layout",
29   - "type":"Mage_Core_Block_Template",
30   - "children":{
31   - "header":{ "label":"Header", "type":"Mage_Adminhtml_Block_Page_Header" },
32   - "menu":{ "label":"Top navigation", "type":"Mage_Backend_Block_Menu" },
33   - "breadcrumbs":{ "label":"Breadcrumbs", "type":"Mage_Adminhtml_Block_Widget_Breadcrumbs" },
34   - "content":{ "label":"Content block", "type":"Mage_Core_Block_Template" },
35   - "left":{ "label":"Left navigation", "type":"Mage_Core_Block_Template" },
36   - "footer":{ "label":"Footer", "type":"Mage_Adminhtml_Block_Page_Footer" }
37   - },
38   - "vars":{}
39   -}*/ ?>
40   -
  27 +<?php /** @var $this Mage_Adminhtml_Block_Page */ ?>
41 28 <!doctype html>
42 29 <html lang="<?php echo $this->getLang() ?>" class="no-js">
43 30
@@ -55,7 +42,7 @@
55 42
56 43 <section class="page-content" id="anchor-content">
57 44 <?php echo $this->getChildHtml('main-top'); ?>
58   - <div class="messages">
  45 + <div id="messages" class="messages">
59 46 <?php echo $this->getMessagesBlock()->getGroupedHtml() ?>
60 47 </div>
61 48
145 app/code/core/Mage/Backend/Block/Widget/Grid/Column/Filter/Price.php
@@ -30,12 +30,79 @@
30 30 * @category Mage
31 31 * @package Mage_Backend
32 32 * @author Magento Core Team <core@magentocommerce.com>
  33 + *
  34 + * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
33 35 */
34 36 class Mage_Backend_Block_Widget_Grid_Column_Filter_Price extends Mage_Backend_Block_Widget_Grid_Column_Filter_Abstract
35 37 {
  38 + /**
  39 + * @var array
  40 + */
36 41 protected $_currencyList = null;
  42 +
  43 + /**
  44 + * @var Mage_Directory_Model_Currency
  45 + */
37 46 protected $_currencyModel = null;
38 47
  48 + /**
  49 + * @var Mage_Directory_Model_Currency_DefaultLocator
  50 + */
  51 + protected $_currencyLocator = null;
  52 +
  53 + /**
  54 + * @param Mage_Core_Controller_Request_Http $request
  55 + * @param Mage_Core_Model_Layout $layout
  56 + * @param Mage_Core_Model_Event_Manager $eventManager
  57 + * @param Mage_Backend_Model_Url $urlBuilder
  58 + * @param Mage_Core_Model_Translate $translator
  59 + * @param Mage_Core_Model_Cache $cache
  60 + * @param Mage_Core_Model_Design_Package $designPackage
  61 + * @param Mage_Core_Model_Session $session
  62 + * @param Mage_Core_Model_Store_Config $storeConfig
  63 + * @param Mage_Core_Controller_Varien_Front $frontController
  64 + * @param Mage_Core_Model_Factory_Helper $helperFactory
  65 + * @param Mage_Core_Model_Dir $dirs
  66 + * @param Mage_Core_Model_Logger $logger
  67 + * @param Magento_Filesystem $filesystem
  68 + * @param Mage_Directory_Model_Currency $currencyModel
  69 + * @param Mage_Directory_Model_Currency_DefaultLocator $currencyLocator
  70 + * @param array $data
  71 + *
  72 + * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  73 + */
  74 + public function __construct(
  75 + Mage_Core_Controller_Request_Http $request,
  76 + Mage_Core_Model_Layout $layout,
  77 + Mage_Core_Model_Event_Manager $eventManager,
  78 + Mage_Backend_Model_Url $urlBuilder,
  79 + Mage_Core_Model_Translate $translator,
  80 + Mage_Core_Model_Cache $cache,
  81 + Mage_Core_Model_Design_Package $designPackage,
  82 + Mage_Core_Model_Session $session,
  83 + Mage_Core_Model_Store_Config $storeConfig,
  84 + Mage_Core_Controller_Varien_Front $frontController,
  85 + Mage_Core_Model_Factory_Helper $helperFactory,
  86 + Mage_Core_Model_Dir $dirs,
  87 + Mage_Core_Model_Logger $logger,
  88 + Magento_Filesystem $filesystem,
  89 + Mage_Directory_Model_Currency $currencyModel,
  90 + Mage_Directory_Model_Currency_DefaultLocator $currencyLocator,
  91 + array $data = array()
  92 + ) {
  93 + parent::__construct($request, $layout, $eventManager, $urlBuilder, $translator, $cache, $designPackage,
  94 + $session, $storeConfig, $frontController, $helperFactory, $dirs, $logger, $filesystem, $data
  95 + );
  96 +
  97 + $this->_currencyModel = $currencyModel;
  98 + $this->_currencyLocator = $currencyLocator;
  99 + }
  100 +
  101 + /**
  102 + * Retrieve html
  103 + *
  104 + * @return string
  105 + */
39 106 public function getHtml()
40 107 {
41 108 $html = '<div class="range">';
@@ -63,6 +130,11 @@ public function getHtml()
63 130 return $html;
64 131 }
65 132
  133 + /**
  134 + * Retrieve display currency select
  135 + *
  136 + * @return bool|mixed
  137 + */
66 138 public function getDisplayCurrencySelect()
67 139 {
68 140 if (!is_null($this->getColumn()->getData('display_currency_select'))) {
@@ -72,6 +144,11 @@ public function getDisplayCurrencySelect()
72 144 }
73 145 }
74 146
  147 + /**
  148 + * Retrieve currency affect
  149 + *
  150 + * @return bool|mixed
  151 + */
75 152 public function getCurrencyAffect()
76 153 {
77 154 if (!is_null($this->getColumn()->getData('currency_affect'))) {
@@ -81,20 +158,16 @@ public function getCurrencyAffect()
81 158 }
82 159 }
83 160
84   - protected function _getCurrencyModel()
85   - {
86   - if (is_null($this->_currencyModel)) {
87   - $this->_currencyModel = Mage::getModel('Mage_Directory_Model_Currency');
88   - }
89   -
90   - return $this->_currencyModel;
91   - }
92   -
  161 + /**
  162 + * Retrieve currency select html
  163 + *
  164 + * @return string
  165 + */
93 166 protected function _getCurrencySelectHtml()
94 167 {
95 168 $value = $this->getEscapedValue('currency');
96 169 if (!$value) {
97   - $value = $this->getColumn()->getCurrencyCode();
  170 + $value = $this->_getColumnCurrencyCode();
98 171 }
99 172
100 173 $html = '';
@@ -107,14 +180,25 @@ protected function _getCurrencySelectHtml()
107 180 return $html;
108 181 }
109 182
  183 + /**
  184 + * Retrieve list of currencies
  185 + *
  186 + * @return array|null
  187 + */
110 188 protected function _getCurrencyList()
111 189 {
112 190 if (is_null($this->_currencyList)) {
113   - $this->_currencyList = $this->_getCurrencyModel()->getConfigAllowCurrencies();
  191 + $this->_currencyList = $this->_currencyModel->getConfigAllowCurrencies();
114 192 }
115 193 return $this->_currencyList;
116 194 }
117 195
  196 + /**
  197 + * Retrieve filter value
  198 + *
  199 + * @param null $index
  200 + * @return mixed|null
  201 + */
118 202 public function getValue($index=null)
119 203 {
120 204 if ($index) {
@@ -129,7 +213,11 @@ public function getValue($index=null)
129 213 return null;
130 214 }
131 215
132   -
  216 + /**
  217 + * Retrieve filter condition
  218 + *
  219 + * @return array|mixed|null
  220 + */
133 221 public function getCondition()
134 222 {
135 223 $value = $this->getValue();
@@ -137,9 +225,9 @@ public function getCondition()
137 225 if (isset($value['currency']) && $this->getCurrencyAffect()) {
138 226 $displayCurrency = $value['currency'];
139 227 } else {
140   - $displayCurrency = $this->getColumn()->getCurrencyCode();
  228 + $displayCurrency = $this->_getColumnCurrencyCode();
141 229 }
142   - $rate = $this->_getRate($displayCurrency, $this->getColumn()->getCurrencyCode());
  230 + $rate = $this->_getRate($displayCurrency, $this->_getColumnCurrencyCode());
143 231
144 232 if (isset($value['from'])) {
145 233 $value['from'] *= $rate;
@@ -153,14 +241,37 @@ public function getCondition()
153 241 return $value;
154 242 }
155 243
156   - protected function _getRate($from, $to)
  244 + /**
  245 + * Retrieve column currency code
  246 + *
  247 + * @return string
  248 + */
  249 + protected function _getColumnCurrencyCode()
  250 + {
  251 + return $this->getColumn()->getCurrencyCode()?
  252 + $this->getColumn()->getCurrencyCode() : $this->_currencyLocator->getDefaultCurrency($this->_request);
  253 + }
  254 +
  255 + /**
  256 + * Get currency rate
  257 + *
  258 + * @param $fromRate
  259 + * @param $toRate
  260 + * @return float
  261 + */
  262 + protected function _getRate($fromRate, $toRate)
157 263 {
158   - return Mage::getModel('Mage_Directory_Model_Currency')->load($from)->getAnyRate($to);
  264 + return $this->_currencyModel->load($fromRate)->getAnyRate($toRate);
159 265 }
160 266
  267 + /**
  268 + * Prepare currency rates
  269 + *
  270 + * @param $displayCurrency
  271 + */
161 272 public function prepareRates($displayCurrency)
162 273 {
163   - $storeCurrency = $this->getColumn()->getCurrencyCode();
  274 + $storeCurrency = $this->_getColumnCurrencyCode();
164 275
165 276 $rate = $this->_getRate($storeCurrency, $displayCurrency);
166 277 if ($rate) {
116 app/code/core/Mage/Backend/Block/Widget/Grid/Massaction/Additional.php
... ... @@ -0,0 +1,116 @@
  1 +<?php
  2 +/**
  3 + * Magento
  4 + *
  5 + * NOTICE OF LICENSE
  6 + *
  7 + * This source file is subject to the Open Software License (OSL 3.0)
  8 + * that is bundled with this package in the file LICENSE.txt.
  9 + * It is also available through the world-wide-web at this URL:
  10 + * http://opensource.org/licenses/osl-3.0.php
  11 + * If you did not receive a copy of the license and are unable to
  12 + * obtain it through the world-wide-web, please send an email
  13 + * to license@magentocommerce.com so we can send you a copy immediately.
  14 + *
  15 + * DISCLAIMER
  16 + *
  17 + * Do not edit or add to this file if you wish to upgrade Magento to newer
  18 + * versions in the future. If you wish to customize Magento for your
  19 + * needs please refer to http://www.magentocommerce.com for more information.
  20 + *
  21 + * @category Mage
  22 + * @package Mage_Backend
  23 + * @copyright Copyright (c) 2013 X.commerce, Inc. (http://www.magentocommerce.com)
  24 + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25 + */
  26 +
  27 +/**
  28 + * Backend widget grid massaction additional action
  29 + *
  30 + * @category Mage
  31 + * @package Mage_Backend
  32 + * @author Magento Core Team <core@magentocommerce.com>
  33 + *
  34 + * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  35 + */
  36 +class Mage_Backend_Block_Widget_Grid_Massaction_Additional extends Mage_Backend_Block_Widget_Form
  37 +{
  38 + /**
  39 + * @var Mage_Core_Model_Layout_Argument_HandlerFactory
  40 + */
  41 + protected $_handlerFactory;
  42 +
  43 + /**
  44 + * @param Mage_Core_Controller_Request_Http $request
  45 + * @param Mage_Core_Model_Layout $layout
  46 + * @param Mage_Core_Model_Event_Manager $eventManager
  47 + * @param Mage_Backend_Model_Url $urlBuilder
  48 + * @param Mage_Core_Model_Translate $translator
  49 + * @param Mage_Core_Model_Cache $cache
  50 + * @param Mage_Core_Model_Design_Package $designPackage
  51 + * @param Mage_Core_Model_Session $session
  52 + * @param Mage_Core_Model_Store_Config $storeConfig
  53 + * @param Mage_Core_Controller_Varien_Front $frontController
  54 + * @param Mage_Core_Model_Factory_Helper $helperFactory
  55 + * @param Mage_Core_Model_Dir $dirs
  56 + * @param Mage_Core_Model_Logger $logger
  57 + * @param Magento_Filesystem $filesystem
  58 + * @param Mage_Core_Model_Layout_Argument_HandlerFactory $handlerFactory
  59 + * @param array $data
  60 + *
  61 + * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  62 + */
  63 + public function __construct(
  64 + Mage_Core_Controller_Request_Http $request,
  65 + Mage_Core_Model_Layout $layout,
  66 + Mage_Core_Model_Event_Manager $eventManager,
  67 + Mage_Backend_Model_Url $urlBuilder,
  68 + Mage_Core_Model_Translate $translator,
  69 + Mage_Core_Model_Cache $cache,
  70 + Mage_Core_Model_Design_Package $designPackage,
  71 + Mage_Core_Model_Session $session,
  72 + Mage_Core_Model_Store_Config $storeConfig,
  73 + Mage_Core_Controller_Varien_Front $frontController,
  74 + Mage_Core_Model_Factory_Helper $helperFactory,
  75 + Mage_Core_Model_Dir $dirs,
  76 + Mage_Core_Model_Logger $logger,
  77 + Magento_Filesystem $filesystem,
  78 + Mage_Core_Model_Layout_Argument_HandlerFactory $handlerFactory,
  79 + array $data = array()
  80 + ) {
  81 + parent::__construct($request, $layout, $eventManager, $urlBuilder, $translator, $cache, $designPackage,
  82 + $session, $storeConfig, $frontController, $helperFactory, $dirs, $logger, $filesystem, $data);
  83 +
  84 + $this->_handlerFactory = $handlerFactory;
  85 + }
  86 +
  87 + /**
  88 + * Prepare form before rendering HTML
  89 + *
  90 + * @return Mage_Backend_Block_Widget_Form
  91 + */
  92 + protected function _prepareForm()
  93 + {
  94 + $form = new Varien_Data_Form();
  95 + foreach ($this->getData('fields') as $itemId => $item) {
  96 + $this->_prepareFormItem($item);
  97 + $form->addField($itemId, $item['type'], $item);
  98 + }
  99 + $this->setForm($form);
  100 + return $this;
  101 + }
  102 +
  103 + /**
  104 + * Prepare form item
  105 + *
  106 + * @param array $item
  107 + */
  108 + protected function _prepareFormItem(array &$item)
  109 + {
  110 + if ($item['type'] == 'select' && is_string($item['values'])) {
  111 + $argumentHandler = $this->_handlerFactory->getArgumentHandlerByType('options');
  112 + $item['values'] = $argumentHandler->process($item['values']);
  113 + }
  114 + $item['class'] = isset($item['class']) ? $item['class'] . ' absolute-advice' : 'absolute-advice';
  115 + }
  116 +}
22 app/code/core/Mage/Catalog/Model/Product.php
@@ -115,9 +115,9 @@ class Mage_Catalog_Model_Product extends Mage_Catalog_Model_Abstract
115 115 /**
116 116 * @param Mage_Core_Model_Event_Manager $eventDispatcher
117 117 * @param Mage_Core_Model_Cache $cacheManager
118   - * @param array $data
119 118 * @param Mage_Catalog_Model_Resource_Product $resource
120 119 * @param Mage_Catalog_Model_Resource_Product_Collection $resourceCollection
  120 + * @param array $data
121 121 */
122 122 public function __construct(
123 123 Mage_Core_Model_Event_Manager $eventDispatcher,
@@ -184,9 +184,10 @@ public function getUrlModel()
184 184 */
185 185 public function validate()
186 186 {
187   - Mage::dispatchEvent($this->_eventPrefix.'_validate_before', array($this->_eventObject=>$this));
  187 + Mage::dispatchEvent($this->_eventPrefix . '_validate_before', array($this->_eventObject => $this));
  188 + $this->_enforceFunctionalLimitations();
188 189 $result = $this->_getResource()->validate($this);
189   - Mage::dispatchEvent($this->_eventPrefix.'_validate_after', array($this->_eventObject=>$this));
  190 + Mage::dispatchEvent($this->_eventPrefix . '_validate_after', array($this->_eventObject => $this));
190 191 return $result;
191 192 }
192 193
@@ -475,6 +476,21 @@ protected function _beforeSave()
475 476 }
476 477
477 478 parent::_beforeSave();
  479 + $this->_enforceFunctionalLimitations();
  480 + }
  481 +
  482 + /**
  483 + * Sub-routine for enforcing functional limitations
  484 + *
  485 + * @throws Mage_Core_Exception
  486 + */
  487 + protected function _enforceFunctionalLimitations()
  488 + {
  489 + /** @var $limitation Mage_Catalog_Model_Product_Limitation */
  490 + $limitation = Mage::getObjectManager()->get('Mage_Catalog_Model_Product_Limitation');
  491 + if ($this->isObjectNew() && $limitation->isCreateRestricted()) {
  492 + throw new Mage_Core_Exception($limitation->getCreateRestrictedMessage());
  493 + }
478 494 }
479 495
480 496 /**
93 app/code/core/Mage/Catalog/Model/Product/Limitation.php
... ... @@ -0,0 +1,93 @@
  1 +<?php
  2 +/**
  3 + * Product functional limitations
  4 + *
  5 + * Magento
  6 + *
  7 + * NOTICE OF LICENSE
  8 + *
  9 + * This source file is subject to the Open Software License (OSL 3.0)
  10 + * that is bundled with this package in the file LICENSE.txt.
  11 + * It is also available through the world-wide-web at this URL:
  12 + * http://opensource.org/licenses/osl-3.0.php
  13 + * If you did not receive a copy of the license and are unable to
  14 + * obtain it through the world-wide-web, please send an email
  15 + * to license@magentocommerce.com so we can send you a copy immediately.
  16 + *
  17 + * DISCLAIMER
  18 + *
  19 + * Do not edit or add to this file if you wish to upgrade Magento to newer
  20 + * versions in the future. If you wish to customize Magento for your
  21 + * needs please refer to http://www.magentocommerce.com for more information.
  22 + *
  23 + * @copyright Copyright (c) 2013 X.commerce, Inc. (http://www.magentocommerce.com)
  24 + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25 + */
  26 +class Mage_Catalog_Model_Product_Limitation
  27 +{
  28 + /**
  29 + * XML-node that stores limitation of number of products in the system
  30 + */
  31 + const XML_PATH_NUM_PRODUCTS = 'limitations/catalog_product';
  32 +
  33 + /**
  34 + * @var Mage_Catalog_Model_Resource_Product
  35 + */
  36 + private $_resource;
  37 +
  38 + /**
  39 + * @var Mage_Core_Model_Config
  40 + */
  41 + private $_config;
  42 +
  43 + /**
  44 + * Inject dependencies
  45 + *
  46 + * @param Mage_Catalog_Model_Resource_Product $resource
  47 + * @param Mage_Core_Model_Config $config
  48 + */
  49 + public function __construct(Mage_Catalog_Model_Resource_Product $resource, Mage_Core_Model_Config $config)
  50 + {
  51 + $this->_resource = $resource;
  52 + $this->_config = $config;
  53 + }
  54 +
  55 + /**
  56 + * Whether creation is restricted
  57 + *
  58 + * @param int $num Number of products to create
  59 + * @return bool
  60 + */
  61 + public function isCreateRestricted($num = 1)
  62 + {
  63 + $limit = (int)$this->_config->getNode(self::XML_PATH_NUM_PRODUCTS);
  64 + if ($limit > 0) {
  65 + return $this->_resource->countAll() + $num > $limit;
  66 + }
  67 + return false;
  68 + }
  69 +
  70 + /**
  71 + * Whether adding new product is restricted
  72 + *
  73 + * @return bool
  74 + */
  75 + public function isNewRestricted()
  76 + {
  77 + $limit = (int)$this->_config->getNode(self::XML_PATH_NUM_PRODUCTS);
  78 + if ($limit > 0) {
  79 + return $this->_resource->countAll() + 1 >= $limit;
  80 + }
  81 + return false;
  82 + }
  83 +
  84 + /**
  85 + * Get message with the the restriction explanation
  86 + *
  87 + * @return string
  88 + */
  89 + public function getCreateRestrictedMessage()
  90 + {
  91 + return Mage::helper('Mage_Catalog_Helper_Data')->__('Maximum allowed number of products is reached.');
  92 + }
  93 +}
14 app/code/core/Mage/Catalog/Model/Resource/Product.php
@@ -691,4 +691,18 @@ public function getAssignedImages($product, $storeIds)
691 691 $images = $read->fetchAll($select);
692 692 return $images;
693 693 }
  694 +
  695 + /**
  696 + * Get total number of records in the system
  697 + *
  698 + * @return int
  699 + */
  700 + public function countAll()
  701 + {
  702 + $adapter = $this->_getReadAdapter();
  703 + $select = $adapter->select();
  704 + $select->from($this->getEntityTable(), 'COUNT(*)');
  705 + $result = (int)$adapter->fetchOne($select);
  706 + return $result;
  707 + }
694 708 }
2  app/code/core/Mage/Core/Controller/Varien/Front.php
@@ -151,7 +151,7 @@ public function init()
151 151
152 152 $routersInfo = array_merge(
153 153 Mage::app()->getConfig()->getRouters(),
154   - Mage::app()->getStore()->getConfig(self::XML_STORE_ROUTERS_PATH)
  154 + Mage::app()->getStore()->getConfig(self::XML_STORE_ROUTERS_PATH) ?: array()
155 155 );
156 156
157 157 Magento_Profiler::start('collect_routers');
18 app/code/core/Mage/Core/Helper/Data.php
@@ -36,7 +36,6 @@ class Mage_Core_Helper_Data extends Mage_Core_Helper_Abstract
36 36 const XML_PATH_PUBLIC_FILES_VALID_PATHS = 'general/file/public_files_valid_paths';
37 37 const XML_PATH_ENCRYPTION_MODEL = 'global/helpers/core/encryption_model';
38 38 const XML_PATH_DEV_ALLOW_IPS = 'dev/restrict/allow_ips';
39   - const XML_PATH_CACHE_BETA_TYPES = 'global/cache/betatypes';
40 39 const XML_PATH_CONNECTION_TYPE = 'global/resources/default_setup/connection/type';
41 40 const XML_PATH_IMAGE_ADAPTER = 'dev/image/adapter';
42 41 const XML_PATH_STATIC_FILE_SIGNATURE = 'dev/static/sign';
@@ -402,23 +401,6 @@ public function getCacheTypes()
402 401 }
403 402
404 403 /**
405   - * Get information about available cache beta types
406   - *
407   - * @return array
408   - */
409   - public function getCacheBetaTypes()
410   - {
411   - $types = array();
412   - $config = Mage::getConfig()->getNode(self::XML_PATH_CACHE_BETA_TYPES);
413   - if ($config) {
414   - foreach ($config->children() as $type=>$node) {
415   - $types[$type] = (string)$node->label;
416   - }
417   - }
418   - return $types;
419   - }
420   -
421   - /**
422 404 * Copy data from object|array to object|array containing fields
423 405 * from fieldset matching an aspect.
424 406 *
4 app/code/core/Mage/Core/Model/Abstract.php
@@ -500,7 +500,7 @@ protected function _getValidationRulesBeforeSave()
500 500 * Get list of cache tags applied to model object.
501 501 * Return false if cache tags are not supported by model
502 502 *
503   - * @return array|false
  503 + * @return array|bool
504 504 */
505 505 public function getCacheTags()
506 506 {
@@ -574,7 +574,7 @@ protected function _afterSave()
574 574 /**
575 575 * Delete object from database
576 576 *
577   - * @return Mage_ Core_Model_Abstract
  577 + * @return Mage_Core_Model_Abstract
578 578 * @throws Exception
579 579 */
580 580 public function delete()
5 app/code/core/Mage/Core/Model/App.php
@@ -534,6 +534,11 @@ public function setRequest(Mage_Core_Controller_Request_Http $request)
534 534 */
535 535 public function getResponse()
536 536 {
  537 + if (!$this->_response) {
  538 + $this->_response = $this->_objectManager->get('Mage_Core_Controller_Response_Http');
  539 + $this->_response->headersSentThrowsException = Mage::$headersSentThrowsException;
  540 + $this->_response->setHeader('Content-Type', 'text/html; charset=UTF-8');
  541 + }
537 542 return $this->_response;
538 543 }
539 544
18 app/code/core/Mage/Core/Model/Cache.php
@@ -37,11 +37,6 @@ class Mage_Core_Model_Cache implements Mage_Core_Model_CacheInterface
37 37 const XML_PATH_TYPES = 'global/cache/types';
38 38
39 39 /**
40   - * Inject custom cache settings in application initialization
41   - */
42   - const APP_INIT_PARAM = 'cache';
43   -
44   - /**
45 40 * @var Mage_Core_Model_Config
46 41 */
47 42 protected $_config;
@@ -92,13 +87,6 @@ class Mage_Core_Model_Cache implements Mage_Core_Model_CacheInterface
92 87 );
93 88
94 89 /**
95   - * List of available request processors
96   - *
97   - * @var array
98   - */
99   - protected $_requestProcessors = array();
100   -
101   - /**
102 90 * Disallow cache saving
103 91 *
104 92 * @var bool
@@ -118,7 +106,7 @@ class Mage_Core_Model_Cache implements Mage_Core_Model_CacheInterface
118 106 protected $_globalBanUseCache = false;
119 107
120 108 /**
121   - * @param Mage_Core_Model_Config $config
  109 + * @param Mage_Core_Model_ConfigInterface $config
122 110 * @param Mage_Core_Model_Config_Primary $cacheConfig
123 111 * @param Mage_Core_Model_Dir $dirs
124 112 * @param Mage_Core_Model_Factory_Helper $helperFactory
@@ -173,10 +161,6 @@ public function __construct(
173 161 // stop profiling
174 162 Magento_Profiler::stop('cache_frontend_create');
175 163
176   - if (isset($options['request_processors'])) {
177   - $this->_requestProcessors = $options['request_processors'];
178   - }
179   -
180 164 if (isset($options['disallow_save'])) {
181 165 $this->_disallowSave = $options['disallow_save'];
182 166 }
51 app/code/core/Mage/Core/Model/Config.php
@@ -56,6 +56,56 @@ class Mage_Core_Model_Config implements Mage_Core_Model_ConfigInterface
56 56 protected $_secureUrlCache = array();
57 57
58 58 /**
  59 + * Resource model
  60 + * Used for operations with DB
  61 + *
  62 + * @var Mage_Core_Model_Resource_Config
  63 + */
  64 + protected $_resourceModel;
  65 +
  66 + /**
  67 + * Configuration data model
  68 + *
  69 + * @var Mage_Core_Model_Config_Data
  70 + */
  71 + protected $_configDataModel;
  72 +
  73 + /**
  74 + * Configuration for events by area
  75 + *
  76 + * @var array
  77 + */
  78 + protected $_eventAreas;
  79 +
  80 + /**
  81 + * Flag cache for existing or already created directories
  82 + *
  83 + * @var array
  84 + */
  85 + protected $_dirExists = array();
  86 +
  87 + /**
  88 + * Flach which allow using cache for config initialization
  89 + *
  90 + * @var bool
  91 + */
  92 + protected $_allowCacheForInit = true;
  93 +
  94 + /**
  95 + * Property used during cache save process
  96 + *
  97 + * @var array
  98 + */
  99 + protected $_cachePartsForSave = array();
  100 +
  101 + /**
  102 + * Empty configuration object for loading and merging configuration parts
  103 + *
  104 + * @var Mage_Core_Model_Config_Base
  105 + */
  106 + protected $_prototype;
  107 +
  108 + /**
59 109 * Active modules array per namespace
60 110 *
61 111 * @var array
@@ -583,6 +633,7 @@ public function reinit()
583 633 $this->removeCache();
584 634 $this->_invalidator->invalidate();
585 635 $this->_config = $this->_storage->getConfiguration();
  636 + $this->_cacheInstanceId = null;
586 637 }
587 638
588 639 /**
20 app/code/core/Mage/Core/Model/Design.php
@@ -48,6 +48,13 @@ class Mage_Core_Model_Design extends Mage_Core_Model_Abstract
48 48 const CACHE_TAG = 'CORE_DESIGN';
49 49
50 50 /**
  51 + * Prefix of model events names
  52 + *
  53 + * @var string
  54 + */
  55 + protected $_eventPrefix = 'core_design';
  56 +
  57 + /**
51 58 * Model cache tag for clear cache in after save and after delete
52 59 *
53 60 * When you use true - all cache will be clean
@@ -78,13 +85,13 @@ public function loadChange($storeId, $date = null)
78 85 }
79 86
80 87 $changeCacheId = 'design_change_' . md5($storeId . $date);
81   - $result = Mage::app()->loadCache($changeCacheId);
  88 + $result = $this->_cacheManager->load($changeCacheId);
82 89 if ($result === false) {
83 90 $result = $this->getResource()->loadChange($storeId, $date);
84 91 if (!$result) {
85 92 $result = array();
86 93 }
87   - Mage::app()->saveCache(serialize($result), $changeCacheId, array(self::CACHE_TAG), 86400);
  94 + $this->_cacheManager->save(serialize($result), $changeCacheId, array(self::CACHE_TAG), 86400);
88 95 } else {
89 96 $result = unserialize($result);
90 97 }
@@ -99,13 +106,14 @@ public function loadChange($storeId, $date = null)
99 106 /**
100 107 * Apply design change from self data into specified design package instance
101 108 *
102   - * @param Mage_Core_Model_Design_Package $packageInto
  109 + * @param Mage_Core_Model_Design_PackageInterface $packageInto
103 110 * @return Mage_Core_Model_Design
104 111 */
105   - public function changeDesign(Mage_Core_Model_Design_Package $packageInto)
  112 + public function changeDesign(Mage_Core_Model_Design_PackageInterface $packageInto)
106 113 {
107   - if ($this->getDesign()) {
108   - $packageInto->setDesignTheme($this->getDesign());
  114 + $design = $this->getDesign();
  115 + if ($design) {
  116 + $packageInto->setDesignTheme($design);
109 117 }
110 118 return $this;
111 119 }
225 app/code/core/Mage/Core/Model/Design/Package/Proxy.php
... ... @@ -1,225 +0,0 @@
1   -<?php
2   -/**
3   - * Magento
4   - *
5   - * NOTICE OF LICENSE
6   - *
7   - * This source file is subject to the Open Software License (OSL 3.0)
8   - * that is bundled with this package in the file LICENSE.txt.
9   - * It is also available through the world-wide-web at this URL: