Compare commits

...

10 Commits

Author SHA1 Message Date
Taber
d4bc6f8562 Fix delete functionality for vendor notes dynamic rows
- Changed row ID prefix from 'row_' to '_' for valid CSS selectors
- Added _id property assignment in row data for proper rendering
- Resolves querySelectorAll SyntaxError when deleting rows

The issue was that numeric row IDs (0, 1, 2) created invalid CSS
selectors like 'tr#0 button.action-delete'. CSS IDs cannot start
with numbers. By prefixing with underscore, we get valid selectors
like 'tr#_0 button.action-delete'.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 12:58:58 -05:00
shopkeeperdev
296520df97 Add complete author information with homepage and role
- Name: Taber
- Email: taber@shopkeeper.dev
- Homepage: https://shopkeeper.dev
- Role: Developer

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 15:49:37 -05:00
shopkeeperdev
36dcdfcafa Update author information
- Changed author name to Taber
- Confirmed email as taber@shopkeeper.dev

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 15:30:11 -05:00
shopkeeperdev
381544f4f7 Update README with new package name and repository URL
- Changed package name from shopkeeper/vendornotes to shopkeeper/module-vendor-notes
- Updated repository URL to code.shopkeeper.dev

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 15:14:45 -05:00
shopkeeperdev
2b8cf4f0bc Remove legacy extra.map configuration
- Removed extra.map section that was causing duplicate installation paths
- Module will now install correctly to vendor/ directory only
- Magento's component registration handles module loading

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 14:36:00 -05:00
shopkeeperdev
677b43db02 Rename package to shopkeeper/module-vendor-notes and fix delete button issue
- Update package name from shopkeeper/vendornotes to shopkeeper/module-vendor-notes to follow naming conventions
- Fix invalid CSS selector issue by prefixing numeric row IDs with 'row_' in getArrayRows()
- Resolves JavaScript error: "Failed to execute 'querySelectorAll' on 'Document': 'tr#0 button.action-delete' is not a valid selector"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 14:23:40 -05:00
shopkeeperdev
2340e2bf79 Changed name in composer.json. 2025-10-11 10:54:04 -04:00
shopkeeperdev
179979342e Fix to make vendor attribute user-defined, add to Default attribute set 2025-10-08 18:18:37 -04:00
shopkeeperdev
6a75fa3a5d Added support for Composer 2025-10-08 12:07:00 -04:00
shopkeeperdev
7062fd8db2 Support for HTML content 2025-10-08 09:34:26 -04:00
6 changed files with 158 additions and 11 deletions

View File

@@ -6,6 +6,17 @@ use Magento\Framework\DataObject;
class VendorMapping extends AbstractFieldArray class VendorMapping extends AbstractFieldArray
{ {
/**
* Initialise form fields
*
* @return void
*/
protected function _construct()
{
parent::_construct();
$this->setTemplate('Magento_Config::system/config/form/field/array.phtml');
}
/** /**
* Prepare rendering the new field by adding all the needed columns * Prepare rendering the new field by adding all the needed columns
*/ */
@@ -27,6 +38,30 @@ class VendorMapping extends AbstractFieldArray
$this->_addButtonLabel = __('Add Vendor Note'); $this->_addButtonLabel = __('Add Vendor Note');
} }
/**
* Obtain existing data from form element
*
* Each row will be instance of Varien_Object
*
* @return array
*/
public function getArrayRows()
{
$result = [];
/** @var DataObject $row */
foreach (parent::getArrayRows() as $key => $row) {
// Ensure row IDs are prefixed with underscore to make valid CSS selectors
// CSS IDs cannot start with a number
if (is_numeric($key)) {
$key = '_' . $key;
}
// Also set the _id property in the row data for proper rendering
$row->setData('_id', $key);
$result[$key] = $row;
}
return $result;
}
/** /**
* Prepare existing row data object * Prepare existing row data object
* *

View File

@@ -6,27 +6,23 @@ use Magento\Sales\Model\Order;
use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\ScopeInterface;
use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\Serialize\Serializer\Json;
use Magento\Framework\Filter\Template as FilterTemplate;
class VendorNotes extends Template class VendorNotes extends Template
{ {
protected $_order; protected $_order;
protected $_scopeConfig; protected $_scopeConfig;
protected $json; protected $json;
protected $filterTemplate;
public function __construct( public function __construct(
\Magento\Backend\Block\Template\Context $context, \Magento\Backend\Block\Template\Context $context,
\Magento\Sales\Model\Order $order, \Magento\Sales\Model\Order $order,
ScopeConfigInterface $scopeConfig, ScopeConfigInterface $scopeConfig,
Json $json, Json $json,
FilterTemplate $filterTemplate,
array $data = [] array $data = []
) { ) {
$this->_order = $order; $this->_order = $order;
$this->_scopeConfig = $scopeConfig; $this->_scopeConfig = $scopeConfig;
$this->json = $json; $this->json = $json;
$this->filterTemplate = $filterTemplate;
parent::__construct($context, $data); parent::__construct($context, $data);
} }
@@ -98,4 +94,20 @@ class VendorNotes extends Template
return []; return [];
} }
} }
/**
* Filter output to allow safe HTML tags
*
* @param string $content
* @return string
*/
public function filterOutputHtml($content)
{
// Decode HTML entities first in case the content was double-encoded
$content = html_entity_decode($content, ENT_QUOTES, 'UTF-8');
// Use Magento's filter to allow specific HTML tags
// This is safer than just echoing raw HTML
return $this->filterTemplate->filter($content);
}
} }

View File

@@ -129,3 +129,50 @@ app/code/Shopkeeper/VendorNotes/
## Support ## Support
For issues or questions, please contact your development team. For issues or questions, please contact your development team.
## Installing via Composer
You can install this module into a Magento 2 project using Composer in a few ways.
1) Local path repository (during development)
Add this to your Magento project's `composer.json` repositories section:
```json
{
"type": "path",
"url": "../path/to/VendorNotes",
"options": { "symlink": true }
}
```
Then require the package:
```bash
composer require shopkeeper/module-vendor-notes:1.1.0
```
2) VCS repository
If this module is hosted in a Git repository, add a VCS entry to your project's `composer.json`:
```json
{
"type": "vcs",
"url": "https://code.shopkeeper.dev/McQueen/module-vendor-notes.git"
}
```
Then require it the same way:
```bash
composer require shopkeeper/module-vendor-notes:dev-main
```
After installing, run these Magento commands from your project root:
```bash
php bin/magento module:enable Shopkeeper_VendorNotes
php bin/magento setup:upgrade
php bin/magento cache:flush
```

View File

@@ -9,6 +9,7 @@ use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Entity\Attribute\SetFactory as AttributeSetFactory; use Magento\Eav\Model\Entity\Attribute\SetFactory as AttributeSetFactory;
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory as AttributeSetCollectionFactory; use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory as AttributeSetCollectionFactory;
use Magento\Catalog\Model\Product\Attribute\Backend\Sku;
class InstallData implements InstallDataInterface class InstallData implements InstallDataInterface
{ {
@@ -46,6 +47,7 @@ class InstallData implements InstallDataInterface
'group' => 'General', 'group' => 'General',
'used_in_product_listing' => true, 'used_in_product_listing' => true,
'visible_on_front' => false, 'visible_on_front' => false,
'is_user_defined' => true,
'option' => [ 'option' => [
'values' => [ 'values' => [
'Thermo Fisher', 'Thermo Fisher',
@@ -65,8 +67,29 @@ class InstallData implements InstallDataInterface
$attributeSetCollection->addFieldToFilter('entity_type_id', $entityTypeId); $attributeSetCollection->addFieldToFilter('entity_type_id', $entityTypeId);
foreach ($attributeSetCollection as $attributeSet) { foreach ($attributeSetCollection as $attributeSet) {
$groupId = $eavSetup->getAttributeGroupId($entityTypeId, $attributeSet->getId(), 'General'); // Get the General group ID, or fallback to the first available group
$eavSetup->addAttributeToGroup($entityTypeId, $attributeSet->getId(), $groupId, $attributeId, null); try {
$groupId = $eavSetup->getAttributeGroupId(
$entityTypeId,
$attributeSet->getId(),
'General'
);
} catch (\Exception $e) {
// If General group doesn't exist, get the default group
$groupId = $eavSetup->getDefaultAttributeGroupId(
$entityTypeId,
$attributeSet->getId()
);
}
// Add attribute to the set
$eavSetup->addAttributeToGroup(
$entityTypeId,
$attributeSet->getId(),
$groupId,
$attributeId,
null // Sort order
);
} }
$setup->endSetup(); $setup->endSetup();

27
composer.json Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "shopkeeper/module-vendor-notes",
"description": "Magento 2 module that adds vendor notes to orders",
"type": "magento2-module",
"license": "MIT",
"version": "1.1.0",
"require": {
"php": ">=7.1",
"magento/framework": "*"
},
"autoload": {
"psr-4": {
"Shopkeeper\\VendorNotes\\": ""
},
"files": [
"registration.php"
]
},
"authors": [
{
"name": "Taber",
"email": "taber@shopkeeper.dev",
"homepage": "https://shopkeeper.dev",
"role": "Developer"
}
]
}

View File

@@ -15,7 +15,10 @@ $notes = $block->getVendorNotes();
<span class="title"><?php echo __('Vendor Note %1', $index + 1); ?></span> <span class="title"><?php echo __('Vendor Note %1', $index + 1); ?></span>
</div> </div>
<div class="vendor-note-content" style="padding: 10px 0; line-height: 1.6;"> <div class="vendor-note-content" style="padding: 10px 0; line-height: 1.6;">
<?php echo $this->filterOutputHtml($note); ?> <?php
// Decode HTML entities and output
echo html_entity_decode($note, ENT_QUOTES | ENT_HTML5, 'UTF-8');
?>
</div> </div>
</div> </div>
<?php if ($index < count($notes) - 1): ?> <?php if ($index < count($notes) - 1): ?>