partkeepr

fork of partkeepr
git clone https://git.e1e0.net/partkeepr.git
Log | Files | Refs | Submodules | README | LICENSE

commit c710ebee94a52133aca093bcd1688fbdef3b9ffd
parent 589beae11687158d5ddd75fa96aa02804004bfdc
Author: Felicia Hummel <felicia@partkeepr.com>
Date:   Mon,  2 Oct 2017 14:30:21 +0200

Merge branch 'master' of github.com:partkeepr/PartKeepr

Diffstat:
MCHANGELOG.md | 11+++++++++++
Mapp/PartKeeprRequirements.php | 27++++++++++++++++-----------
Mapp/config/parameters.php.dist | 8++++++++
Asrc/PartKeepr/CoreBundle/Command/FixTreeCommand.php | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PartKeepr/CoreBundle/DependencyInjection/Configuration.php | 5+++++
Msrc/PartKeepr/CoreBundle/DependencyInjection/PartKeeprCoreExtension.php | 3++-
Msrc/PartKeepr/CoreBundle/Services/SystemService.php | 19+++++++++++++++++++
Msrc/PartKeepr/FrontendBundle/Controller/IndexController.php | 1+
Msrc/PartKeepr/FrontendBundle/Resources/public/css/PartKeepr.css | 18+++++++++++++++++-
Asrc/PartKeepr/FrontendBundle/Resources/public/images/become_a_patron_button.png | 0
Asrc/PartKeepr/FrontendBundle/Resources/public/images/partkeepr-header.png | 0
Asrc/PartKeepr/FrontendBundle/Resources/public/images/patreon.png | 0
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/MenuBar.js | 41++++++++++++++++++++++++++++++++++-------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsManager.js | 10+++++-----
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/PatreonStatusDialog.js | 19+++++++++++++++++++
Msrc/PartKeepr/FrontendBundle/Resources/views/index.html.twig | 1+
Msrc/PartKeepr/PartBundle/Entity/PartDistributor.php | 2+-
17 files changed, 284 insertions(+), 26 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## PartKeepr 1.3.0 + +New Features: + + * [Refactored Project Reports](http://wiki.partkeepr.org/wiki/New_and_Noteworthy/1.3.0#Refactored_Project_Reports) + * [Distributor Price Calculation](http://wiki.partkeepr.org/wiki/New_and_Noteworthy/1.3.0#Distributor_Price_Calculation): Ignore distributors for price calculations + * [Column Customization](http://wiki.partkeepr.org/wiki/New_and_Noteworthy/1.3.0#Column_Customization) + * [Selective OctoPart Import](http://wiki.partkeepr.org/wiki/New_and_Noteworthy/1.3.0#Selective_OctoPart_Import) + * [Refactored User Preferences](http://wiki.partkeepr.org/wiki/New_and_Noteworthy/1.3.0#Refactored_User_Preferences) + +For a mostly complete list of bugfixes, please refer to [GitHub](https://github.com/partkeepr/PartKeepr/issues?q=milestone%3A1.3.0+is%3Aclosed) ## PartKeepr 1.2.0 diff --git a/app/PartKeeprRequirements.php b/app/PartKeeprRequirements.php @@ -20,6 +20,11 @@ class PartKeeprRequirements extends SymfonyRequirements sprintf('Install the GD library extension')); $this->addRequirement( + function_exists('ldap_connect'), + sprintf('LDAP library not found'), + sprintf('Install the LDAP library extension')); + + $this->addRequirement( function_exists('curl_init'), sprintf('CURL library not found'), sprintf('Install the CURL library extension')); @@ -30,9 +35,9 @@ class PartKeeprRequirements extends SymfonyRequirements sprintf('The php.ini memory_limit directive must be set to 128MB or higher. Your limit is set to %s', ini_get('memory_limit'))); - $this->checkWritable(realpath(dirname(__FILE__).'/../data/')); - $this->checkWritable(realpath(dirname(__FILE__).'/../app/')); - $this->checkWritable(realpath(dirname(__FILE__).'/../web/')); + $this->checkWritable(realpath(dirname(__FILE__) . '/../data/')); + $this->checkWritable(realpath(dirname(__FILE__) . '/../app/')); + $this->checkWritable(realpath(dirname(__FILE__) . '/../web/')); $this->addRecommendation( function_exists('apc_fetch'), @@ -98,7 +103,7 @@ class PartKeeprRequirements extends SymfonyRequirements */ protected function getBytesIniSetting($setting) { - return (int) $this->returnBytes(ini_get($setting)); + return (int)$this->returnBytes(ini_get($setting)); } /** @@ -140,24 +145,24 @@ class PartKeeprRequirements extends SymfonyRequirements protected function isWritableRecursive($dir) { if (!is_writable($dir)) { - throw new \Exception($dir.' is not writable.'); + throw new \Exception($dir . ' is not writable.'); } $folder = opendir($dir); while ($file = readdir($folder)) { if ($file != '.' && $file != '..') { - if (!is_writable($dir.'/'.$file)) { + if (!is_writable($dir . '/' . $file)) { closedir($folder); - throw new \Exception($dir.'/'.$file.' is not writable.'); + throw new \Exception($dir . '/' . $file . ' is not writable.'); } else { // Skip hidden directories - if ((is_dir($dir.'/'.$file)) && ($file[0] == '.')) { + if ((is_dir($dir . '/' . $file)) && ($file[0] == '.')) { continue; } - if (is_dir($dir.'/'.$file)) { - if (!$this->isWritableRecursive($dir.'/'.$file)) { + if (is_dir($dir . '/' . $file)) { + if (!$this->isWritableRecursive($dir . '/' . $file)) { closedir($folder); - throw new \Exception($dir.'/'.$file.' is not writable.'); + throw new \Exception($dir . '/' . $file . ' is not writable.'); } } } diff --git a/app/config/parameters.php.dist b/app/config/parameters.php.dist @@ -249,3 +249,10 @@ $container->setParameter('partkeepr.cronjob.check', true); * https://octopart.com/api/home and then registering an application. */ $container->setParameter('partkeepr.octopart.apikey', ''); + +/** + * Specifies which URL contains the patreon status. If you do not wish to display the patreon status, + * set this parameter to false. Please note that we rely on your Patreon pledges to ensure further + * development of PartKeepr. + */ +$container->setParameter('partkeepr.patreon.statusuri', 'https://www.partkeepr.org/patreon.json');+ \ No newline at end of file diff --git a/src/PartKeepr/CoreBundle/Command/FixTreeCommand.php b/src/PartKeepr/CoreBundle/Command/FixTreeCommand.php @@ -0,0 +1,145 @@ +<?php + +namespace PartKeepr\CoreBundle\Command; + +use Doctrine\ORM\EntityManager; +use Gedmo\Tree\Entity\Repository\NestedTreeRepository; +use PartKeepr\CategoryBundle\Entity\CategoryPathInterface; +use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class FixTreeCommand extends ContainerAwareCommand +{ + /** + * @var EntityManager + */ + private $entityManager; + + public function configure() + { + parent::configure(); + $this->setName('partkeepr:fix-category-trees'); + $this->setDescription('Fixes the category trees'); + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $this->entityManager = $this->getContainer()->get("doctrine.orm.default_entity_manager"); + $this->fixTree('PartCategory'); + $this->fixTree('FootprintCategory'); + $this->fixTree('StorageLocationCategory'); + } + + /** + * Fixes the tree for a given table due to the migration of doctrine2-nestedset to DoctrineExtensions. + * + * @param string $table The table name to fix + * + * @throws \Doctrine\DBAL\DBALException + */ + public function fixTree($table) + { + $nodes = $this->getNodeIds($table); + + $queryBuilder = $this->entityManager->getConnection()->createQueryBuilder(); + $queryBuilder->update($table) + ->set('parent_id', ':parent') + ->set('root', ':root') + ->set('lvl', ':level') + ->where('id = :id'); + + foreach ($nodes as $node) { + $parent = $this->fetchParent($table, $node['id']); + $level = $this->getLevel($table, $node['id']); + + if ($parent !== false) { + $this->entityManager->getConnection()->executeUpdate( + $queryBuilder->getSQL(), + [ + ':parent' => $parent, + ':id' => $node['id'], + ':level' => $level, + ':root' => 1, + ] + ); + } else { + $this->entityManager->getConnection()->executeUpdate( + $queryBuilder->getSQL(), + [ + ':parent' => null, + ':id' => $node['id'], + ':root' => 1, + ':level' => 0, + ] + ); + } + } + } + + /** + * Fetches the parent node for a table and ID. + * + * @param $table + * @param $id + * + * @return mixed + */ + public function fetchParent($table, $id) + { + $queryBuilder = $this->entityManager->getConnection()->createQueryBuilder(); + + $queryBuilder->select('parent.id') + ->from($table, 'node') + ->from($table, 'parent') + ->where('parent.lft < node.lft') + ->andWhere('parent.rgt > node.rgt') + ->andWhere('node.id = :nodeid') + ->orderBy('parent.rgt - parent.lft') + ->setMaxResults(1); + + return $this->entityManager->getConnection()->fetchColumn($queryBuilder->getSQL(), [':nodeid' => $id], 0); + } + + /** + * Returns the node IDs for the table. + * + * @param $table + * + * @return array + */ + public function getNodeIds($table) + { + $qb = $this->entityManager->getConnection()->createQueryBuilder(); + + $qb->select('id') + ->from($table) + ->orderBy('id', 'ASC'); + + return $this->entityManager->getConnection()->fetchAll($qb->getSQL()); + } + + /** + * Returns the level for a given table and ID. + * + * @param $table + * @param $id + * + * @return mixed + */ + public function getLevel($table, $id) + { + $qb = $this->entityManager->getConnection()->createQueryBuilder(); + + $qb->select('COUNT(*) AS level') + ->from($table, 'node') + ->from($table, 'parent') + ->where('node.lft > parent.lft') + ->andWhere('node.lft < parent.rgt') + ->andWhere('node.id = :nodeid'); + + return $this->entityManager->getConnection()->fetchAssoc($qb->getSQL(), [':nodeid' => $id])['level']; + } + + +} diff --git a/src/PartKeepr/CoreBundle/DependencyInjection/Configuration.php b/src/PartKeepr/CoreBundle/DependencyInjection/Configuration.php @@ -37,6 +37,11 @@ class Configuration implements ConfigurationInterface ->defaultValue('https://partkeepr.org/tips.json') ->info('The URI from where the tip database is loaded') ->end() + ->scalarNode('patreon_status') + ->cannotBeEmpty() + ->defaultValue('https://partkeepr.org/patreon.json') + ->info('The URI from where the patreon status is loaded') + ->end() ->scalarNode('image_cache_directory') ->cannotBeEmpty() ->isRequired() diff --git a/src/PartKeepr/CoreBundle/DependencyInjection/PartKeeprCoreExtension.php b/src/PartKeepr/CoreBundle/DependencyInjection/PartKeeprCoreExtension.php @@ -25,8 +25,9 @@ class PartKeeprCoreExtension extends Extension $container->setParameter('partkeepr.cronjob_check', $config['cronjob_check']); $container->setParameter('partkeepr.image_cache_directory', $config['image_cache_directory']); $container->setParameter('partkeepr.authentication_provider', $config['authentication_provider']); - $container->setParameter('partkeepr.tip_of_the_day_uri', $config['tip_of_the_day_uri']); $container->setParameter('partkeepr.tip_of_the_day_list', $config['tip_of_the_day_list']); + $container->setParameter('partkeepr.tip_of_the_day_uri', $config['tip_of_the_day_uri']); + $container->setParameter('partkeepr.patreon.statusuri', $config['patreon_status']); $container->setParameter('partkeepr.required_cronjobs', $config['required_cronjobs']); foreach ($config['directories'] as $key => $value) { diff --git a/src/PartKeepr/CoreBundle/Services/SystemService.php b/src/PartKeepr/CoreBundle/Services/SystemService.php @@ -2,6 +2,7 @@ namespace PartKeepr\CoreBundle\Services; +use Guzzle\Http\Client; use Doctrine\Bundle\DoctrineBundle\Registry; use Doctrine\DBAL\Version as DBALVersion; use Doctrine\ORM\EntityManager; @@ -284,4 +285,22 @@ class SystemService extends ContainerAware return $size_str; } } + + public function getPatreonStatus () { + $statusURI = $this->container->getParameter("partkeepr.patreon.statusuri"); + + if ($statusURI === false) { + return false; + } + + try { + $client = new Client(); + $request = $client->createRequest('GET', $statusURI, ['timeout' => 3.14]); + $request->send(); + + return json_decode($request->getResponse()->getBody(), true); + } catch (\Exception $e) { + return false; + } + } } diff --git a/src/PartKeepr/FrontendBundle/Controller/IndexController.php b/src/PartKeepr/FrontendBundle/Controller/IndexController.php @@ -65,6 +65,7 @@ class IndexController extends Controller $aParameters['tip_of_the_day_uri'] = $this->getParameter('partkeepr.tip_of_the_day_uri'); $aParameters['password_change'] = $this->getParameterWithDefault('partkeepr.auth.allow_password_change', true); + $aParameters["patreonStatus"] = $this->get("partkeepr_systemservice")->getPatreonStatus(); $renderParams = []; $renderParams['parameters'] = $aParameters; diff --git a/src/PartKeepr/FrontendBundle/Resources/public/css/PartKeepr.css b/src/PartKeepr/FrontendBundle/Resources/public/css/PartKeepr.css @@ -202,4 +202,19 @@ margin-left: -75px; } } -s +.partkeeprLogo { + height: 22px !important; + width: 112px !important; + background-image: url(../images/partkeepr-header.png); +} + +.patreonLogo { + background-image: url(../images/patreon.png); +} + +.patreonButton { + display: block; + height: 51px; + width: 217px; + background-image: url(../images/become_a_patron_button.png); +}+ \ No newline at end of file diff --git a/src/PartKeepr/FrontendBundle/Resources/public/images/become_a_patron_button.png b/src/PartKeepr/FrontendBundle/Resources/public/images/become_a_patron_button.png Binary files differ. diff --git a/src/PartKeepr/FrontendBundle/Resources/public/images/partkeepr-header.png b/src/PartKeepr/FrontendBundle/Resources/public/images/partkeepr-header.png Binary files differ. diff --git a/src/PartKeepr/FrontendBundle/Resources/public/images/patreon.png b/src/PartKeepr/FrontendBundle/Resources/public/images/patreon.png Binary files differ. diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/MenuBar.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/MenuBar.js @@ -1,13 +1,15 @@ Ext.define('PartKeepr.MenuBar', { extend: 'Ext.toolbar.Toolbar', alias: "widget.MenuBar", + + baseCls: Ext.baseCSSPrefix + 'toolbar mainMenu', + menu: { text: "Root", menu: [] }, - createMenu: function (target, menuPath, root) - { + createMenu: function (target, menuPath, root) { var item = menuPath.shift(), newItem; if (item === undefined) { @@ -40,8 +42,7 @@ Ext.define('PartKeepr.MenuBar', { return root; }, - initComponent: function () - { + initComponent: function () { var target, menuItemIterator; this.ui = "mainmenu"; @@ -73,6 +74,7 @@ Ext.define('PartKeepr.MenuBar', { "PartKeepr.StockHistoryGrid" ]; + this.menu.menu.push({xtype: 'tbspacer'}); for (menuItemIterator = 0; menuItemIterator < menuItems.length; menuItemIterator++) { target = Ext.ClassManager.get(menuItems[menuItemIterator]); @@ -104,19 +106,44 @@ Ext.define('PartKeepr.MenuBar', { checked: checked }); } + + this.menu.menu.push({text: i18n("Theme"), type: 'themes', menu: this.themesMenu}); + this.menu.menu.push({xtype: 'tbspacer', width: 50}); + + this.menu.menu.push({ + xtype: 'button', + text: i18n("Patreon Status"), + iconCls: 'patreonLogo', + handler: this.showPatreonStatusDialog, + scope: this + }); + if (Ext.isObject(window.parameters.patreonStatus)) { + this.menu.menu.push({ + xtype: 'progressbar', + value: (window.parameters.patreonStatus.pledgeSum / window.parameters.patreonStatus.goal), + width: 50 + }); + } + this.menu.menu.push({xtype: 'tbfill'}); + this.menu.menu.push({xtype: 'button', iconCls: 'partkeeprLogo'}); + this.menu.menu.push({xtype: 'tbspacer', width: 10}); this.items = this.menu.menu; this.callParent(); }, + showPatreonStatusDialog: function () { + var window = Ext.create("PartKeepr.Components.PatreonStatusDialog"); + window.show(); + }, selectTheme: function (theme) { - var i,j,menuItem; + var i, j, menuItem; - for (i=0;i<this.items.getCount();i++) { + for (i = 0; i < this.items.getCount(); i++) { if (this.items.getAt(i).type === "themes") { - for (j=0;j<this.items.getAt(i).menu.items.getCount();j++) { + for (j = 0; j < this.items.getAt(i).menu.items.getCount(); j++) { menuItem = this.items.getAt(i).menu.items.getAt(j); if (menuItem.theme === theme) { diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsManager.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsManager.js @@ -675,17 +675,17 @@ Ext.define('PartKeepr.PartManager', { if (minValue !== "" && maxValue !== "") { - minMaxCombined = minValue + minSiPrefix + "…" + maxValue + maxSiPrefix + unit; + minMaxCombined = minValue + " " + minSiPrefix + "…" + maxValue + " " + maxSiPrefix + unit; } else { if (minValue !== "") { - minMaxCombined = i18n("Min.") + minValue + minSiPrefix + unit; + minMaxCombined = i18n("Min.") + minValue + " " + minSiPrefix + unit; } if (maxValue !== "") { - minMaxCombined = i18n("Max.") + maxValue + maxSiPrefix + unit; + minMaxCombined = i18n("Max.") + maxValue + " " + maxSiPrefix + unit; } } @@ -693,10 +693,10 @@ Ext.define('PartKeepr.PartManager', { { if (minMaxCombined !== "") { - return value + siPrefix + unit + " (" + minMaxCombined + ")"; + return value + " " + siPrefix + unit + " (" + minMaxCombined + ")"; } else { - return value + siPrefix + unit; + return value + " " + siPrefix + unit; } } else { diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/PatreonStatusDialog.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/PatreonStatusDialog.js @@ -0,0 +1,18 @@ +Ext.define("PartKeepr.Components.PatreonStatusDialog", { + extend: "Ext.window.Window", + + title: i18n("Patreon Status"), + width: 400, + height: 400, + + items: [{ + xtype: 'component', + html: i18n("<h1>PartKeepr needs your support</h1>") + + i18n("<p>We need you to support development, so we can create new cool features, user interface improvements and regular releases!</p>") + + i18n("<p>Every cent helps to keep development alive!</p>") + + i18n('<a href="https://www.patreon.com/partkeepr" target="_blank" class="patreonButton"></a>') + + }] + + +});+ \ No newline at end of file diff --git a/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig b/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig @@ -343,6 +343,7 @@ '@PartKeeprFrontendBundle/Resources/public/js/Components/OctoPart/SearchPanel.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/OctoPart/SearchWindow.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/OctoPart/DataApplicator.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/PatreonStatusDialog.js' '@PartKeeprFrontendBundle/Resources/public/js/php.default.min.js' %} <script type="text/javascript" src="{{ asset_url }}"></script> {% endjavascripts %} diff --git a/src/PartKeepr/PartBundle/Entity/PartDistributor.php b/src/PartKeepr/PartBundle/Entity/PartDistributor.php @@ -84,7 +84,7 @@ class PartDistributor extends BaseEntity /** * Defines if the distributor is ignored for pricing calculations. * - * @ORM\Column(type="boolean") + * @ORM\Column(type="boolean",nullable=true) * @Groups({"default"}) * * @var bool