add my module

This commit is contained in:
Kai Ton 2023-12-04 09:18:00 +07:00
parent e4b66e4508
commit d738ce62b6
50 changed files with 1548 additions and 0 deletions

BIN
app/code/Kai/Banner.zip Executable file

Binary file not shown.

View File

@ -0,0 +1,177 @@
<?php
namespace Kai\Banner\Api;
use Kai\Banner\Api\BannerRepositoryInterface;
/**
* @api
*/
class BannerRepository implements BannerRepositoryInterface
{
protected $kaiBannerModel;
protected $kaiBannerResourceModel;
protected $kaiBannerCollection;
function __construct(
\Kai\Banner\Model\KaiBanner $kaiBannerModel,
\Kai\Banner\Model\KaiBannerResourceModel $kaiBannerResourceModel,
\Kai\Banner\Model\KaiBannerCollection $kaiBannerCollection,
)
{
$this->kaiBannerModel = $kaiBannerModel;
$this->kaiBannerResourceModel = $kaiBannerResourceModel;
$this->kaiBannerCollection = $kaiBannerCollection;
$this->authorization();
}
private function authorization()
{
$headers = apache_request_headers();
$apiKey = \Kai\Banner\Api\Helper::API_KEY;
foreach ($headers as $key => $value) {
$key = strtolower($key);
if ('api_key' === $key && $value === $apiKey) {
return true;
}
}
header('HTTP/1.1 401 Unauthorized');
header('Accept: application/json');
header('Content-Type: application/json');
die(
json_encode(['message' => 'unauthorized'])
);
}
private function responseOk(array $data)
{
header('HTTP/1.1 200 Ok');
header('Accept: application/json');
header('Content-Type: application/json');
die(json_encode($data));
}
private function responseFail(array $data)
{
header('HTTP/1.1 400 Bad request');
header('Accept: application/json');
header('Content-Type: application/json');
die(json_encode($data));
}
private function responseMethodFail()
{
header('HTTP/1.1 400 bad request');
header('Accept: application/json');
header('Content-Type: application/json');
die(json_encode([
'status' => false,
'message' => 'Param ?method=... not exist!'
]));
}
public function getData()
{
$method = $_GET['method'] ?? null;
if ($method === 'show') {
$id = $_GET['id'] ?? 0;
return $this->responseOk([
'status' => true,
'data' => $this
->kaiBannerCollection
->getItemById($id)
->getData()
]);
}
if ($method === 'all') {
return $this->responseOk([
'status' => true,
'data' => $this
->kaiBannerCollection
->getData()
]);
}
if ($method === 'delete') {
$id = $_GET['id'] ?? 0;
$model = $this->kaiBannerModel->setId($id);
try {
$this->kaiBannerResourceModel->delete($model);
return $this->responseOk([
'status' => true,
]);
} catch (\Exception $ex) {
$this->kaiBannerResourceModel->rollBack();
return $this->responseFail([
'status' => false,
'message' => $ex->getMessage()
]);
}
}
return $this->responseMethodFail();
}
public function postData()
{
$method = $_GET['method'] ?? null;
parse_str(
string: file_get_contents('php://input'),
result: $payload
);
if ($method === 'create') {
$model = $this->kaiBannerModel;
try {
foreach ($payload as $key => $value) {
$model->setData($key, $value);
}
$this->kaiBannerResourceModel->save($model);
return $this->responseOk([
'status' => true,
'data' => $this
->kaiBannerCollection
->getItemById($model->getId())
->getData()
]);
} catch (\Exception $ex) {
return $this->responseFail([
'status' => false,
'message' => $ex->getMessage()
]);
}
}
if ($method === 'update') {
$id = $_GET['id'] ?? 0;
$model = $this->kaiBannerModel->setId($id);
try {
foreach ($payload as $key => $value) {
$model->setData($key, $value);
}
$this->kaiBannerResourceModel->save($model);
return $this->responseOk([
'status' => true,
'data' => $this
->kaiBannerCollection
->getItemById($model->getId())
->getData()
]);
} catch (\Exception $ex) {
return $this->responseFail([
'status' => false,
'message' => $ex->getMessage()
]);
}
}
return $this->responseMethodFail();
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Kai\Banner\Api;
/**
* @api
*/
interface BannerRepositoryInterface
{
/**
* method: GET
*
* @return string
*/
public function getData();
/**
* methot: POST
*
* @return string
*/
public function postData();
}

View File

@ -0,0 +1,7 @@
<?php
namespace Kai\Banner\Api;
final class Helper {
public const API_KEY = 'KAI_BANNER_@_123';
}

View File

@ -0,0 +1,43 @@
<?php
namespace Kai\Banner\Block\Adminhtml;
use Magento\Backend\Block\Template;
class Index extends Template
{
protected $urlInterface;
protected $kaiBanner;
protected $kaiBannerCollection;
protected $kaiBannerResourceModel;
public function __construct(
\Magento\Backend\Block\Template\Context $context,
\Magento\Backend\Model\UrlInterface $urlInterface,
\Kai\Banner\Model\KaiBanner $kaiBanner,
\Kai\Banner\Model\KaiBannerResourceModel $kaiBannerResourceModel,
\Kai\Banner\Model\KaiBannerCollection $kaiBannerCollection,
$data = []
)
{
$this->urlInterface = $urlInterface;
$this->kaiBanner = $kaiBanner;
$this->kaiBannerResourceModel = $kaiBannerResourceModel;
$this->kaiBannerCollection = $kaiBannerCollection;
parent::__construct($context, $data);
}
public function getSecretKey()
{
return $this->urlInterface->getSecretKey();
}
public function getApiKey()
{
return \Kai\Banner\Api\Helper::API_KEY;
}
public function getAll()
{
return $this->kaiBannerCollection->getData();
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Kai\Banner\Controller\Adminhtml\Index;
use Magento\Backend\App\Action;
class Index extends Action
{
protected $resultPageFactory = false;
public function __construct(
\Magento\Backend\App\Action\Context $context,
\Magento\Framework\View\Result\PageFactory $resultPageFactory
) {
$this->resultPageFactory = $resultPageFactory;
parent::__construct($context);
}
public function execute()
{
$resultPage = $this->resultPageFactory->create();
$resultPage->getConfig()->getTitle()->prepend(__('Banner'));
return $resultPage;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Kai\Banner\Model;
use Kai\Banner\Model\KaiBannerResourceModel;
use Magento\Backend\Model\Menu\Item\Factory;
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
use Magento\Framework\ObjectManagerInterface;
class KaiBanner extends AbstractModel
{
public function _construct()
{
$this->_init(
resourceModel: KaiBannerResourceModel::class
);
}
}
final class KaiBannerFactory extends Factory
{
protected $objectManager;
protected $instanceName;
public function __construct(ObjectManagerInterface $objectManager, $instanceName = KaiBanner::class)
{
$this->objectManager = $objectManager;
$this->instanceName = $instanceName;
}
public function create(array $arguments = [], AbstractDb $resource = null)
{
return $this->objectManager->create($this->instanceName, $arguments, $resource);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Kai\Banner\Model;
use Kai\Banner\Model\KaiBanner;
use Kai\Banner\Model\KaiBannerResourceModel;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
class KaiBannerCollection extends AbstractCollection
{
public function _construct()
{
$this->_init(
model: KaiBanner::class,
resourceModel: KaiBannerResourceModel::class
);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Kai\Banner\Model;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
class KaiBannerResourceModel extends AbstractDb
{
public function _construct()
{
$this->_init(
mainTable:'kai_banner',
idFieldName:'id'
);
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace Kai\Banner\Model;
use Kai\Banner\Model\KaiBannerCollection;
use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
use Magento\Framework\DB\Ddl\Table;
class KaiBannerValues extends AbstractSource
{
protected $_optionFactory;
protected $collection;
public function __construct(
KaiBannerCollection $collection
) {
$this->collection = $collection;
}
public function getAllOptions()
{
$data = $this->collection->getData();
$options = [
[
'label' => 'Select option!',
'value' => '',
]
];
foreach ($data as $record) {
$options[] = [
'label' => $record['id'],
'value' => $record['title'],
];
};
$this->_options = $options;
return $options;
}
public function getOptionText($value)
{
foreach ($this->getAllOptions() as $option) {
if ($option['value'] == $value) {
return $option['label'];
}
}
return false;
}
public function getFlatColumns()
{
$attributeCode = $this->getAttribute()->getAttributeCode();
return [
$attributeCode => [
'unsigned' => false,
'default' => null,
'extra' => null,
'type' => Table::TYPE_INTEGER,
'nullable' => true,
'comment' => 'Kai Banner ' . $attributeCode . ' column',
],
];
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Kai\Banner\Model\Serialize\Serializer;
use Magento\Framework\Serialize\Serializer\Json as SerializerJson;
class Json extends SerializerJson
{
/**
* @inheritDoc
* @since 101.0.0
*/
public function unserialize($string)
{
if ($string === null) {
throw new \InvalidArgumentException(
'Unable to unserialize value. Error: Parameter must be a string type, null given.'
);
}
$result = json_decode($string, true);
if (json_last_error() !== JSON_ERROR_NONE) {
parse_str($string, $result);
if (is_array($result)) {
return $result;
} else {
throw new \InvalidArgumentException(
$string . " - Unable to unserialize value. Error: " . json_last_error_msg()
);
}
}
return $result;
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Kai\Banner\Setup;
use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\UpgradeDataInterface;
class UpgradeData implements UpgradeDataInterface
{
protected $_eavSetupFactory;
protected $_attributeRepositoryInterface;
protected $_attributeSetupFactory;
public function __construct(
\Magento\Eav\Setup\EavSetupFactory $eavSetupFactory,
\Magento\Eav\Api\AttributeRepositoryInterface $attributeRepositoryInterface,
\Magento\Catalog\Setup\CategorySetupFactory $attributeSetupFactory
) {
$this->_eavSetupFactory = $eavSetupFactory;
$this->_attributeRepositoryInterface = $attributeRepositoryInterface;
$this->_attributeSetupFactory = $attributeSetupFactory;
}
public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
try {
$this->_attributeRepositoryInterface->get(Product::ENTITY, 'kai_banner');
} catch (\Exception $ex) {
$setup->startSetup();
$eavSetup = $this->_eavSetupFactory->create(['setup' => $setup]);
$eavSetup->addAttribute(
Product::ENTITY,
'kai_banner',
[
'input' => 'select',
'type' => 'varchar',
'label' => 'Banner',
'required' => false,
'visible' => true,
'user_defined' => true,
'searchable' => false,
'filterable' => false,
'comparable' => false,
'used_in_product_listing' => true,
'apply_to' => '',
'source' => \Kai\Banner\Model\KaiBannerValues::class,
'global' => ScopedAttributeInterface::SCOPE_GLOBAL,
'group' => 'General',
]
);
$setup->endSetup();
}
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace Kai\Banner\Setup;
use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\Setup\UpgradeSchemaInterface;
class UpgradeSchema implements UpgradeSchemaInterface
{
/**
* {@inheritdoc}
*/
public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$installer = $setup;
$installer->startSetup();
$tableName = $installer->getTable('kai_banner');
if ($installer->tableExists($tableName)) {
$installer->getConnection()->dropTable($tableName);
}
$table = $installer->getConnection()
->newTable($tableName)
->addColumn(
'id',
Table::TYPE_INTEGER,
null,
[
'identity' => true,
'unsigned' => true,
'nullable' => false,
'primary' => true,
],
'ID'
)
->addColumn(
'title',
Table::TYPE_TEXT,
null,
['nullable' => false, 'default' => ''],
'Title'
)
->addColumn(
'html',
Table::TYPE_TEXT,
null,
['nullable' => true, 'default' => ''],
'htmlentities($str)'
)
->addColumn(
'redirect',
Table::TYPE_TEXT,
null,
['nullable' => true, 'default' => ''],
'URL redirect'
)
->addColumn(
'created_at',
Table::TYPE_TIMESTAMP,
null,
[
'nullable' => true,
'default' => Table::TIMESTAMP_INIT,
],
'Created At'
)
->addColumn(
'updated_at',
Table::TYPE_TIMESTAMP,
null,
[
'nullable' => true,
'default' => Table::TIMESTAMP_INIT_UPDATE,
],
'Updated At'
)
->addColumn(
'status',
Table::TYPE_SMALLINT,
null,
[
'nullable' => false,
'default' => '0',
],
'Status'
)->setComment('Kai Banner Module');
$installer->getConnection()->createTable($table);
$installer->endSetup();
}
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0"?>
<config
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd"
>
<acl>
<resources>
<resource id="Magento_Backend::admin">
<resource
id="Kai_Banner::parent"
title="Kai Banner Module"
translate="title"
sortOrder="10"
>
<resource
id="Kai_Banner::index"
title="Index"
translate="title"
sortOrder="10"
/>
</resource>
</resource>
</resources>
</acl>
</config>

View File

@ -0,0 +1,27 @@
<?xml version="1.0"?>
<config
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Kai_Banner:etc/menu.xsd"
>
<menu>
<add
id="Kai_Banner::parent"
title="Kai Module"
module="Kai_Banner"
sortOrder="1"
resource="Magento_Backend::content"
parent="Magento_Backend::content"
/>
<!-- Banner -->
<add
id="Kai_Banner::index"
title="Kai Banner"
module="Kai_Banner"
sortOrder="1"
action="kai_banner/index"
resource="Kai_Banner::parent"
parent="Kai_Banner::parent"
/>
</menu>
</config>

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<config
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
<router id="admin">
<route id="kai_banner" frontName="kai_banner">
<module name="Kai_Banner" before="Magento_Backend" />
</route>
</router>
</config>

12
app/code/Kai/Banner/etc/di.xml Executable file
View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<!-- Repository -->
<preference
for="Kai\Banner\Api\BannerRepositoryInterface"
type="Kai\Banner\Api\BannerRepository"
/>
<preference
for="Magento\Framework\Serialize\Serializer\Json"
type="Kai\Banner\Model\Serialize\Serializer\Json"
/>
</config>

View File

@ -0,0 +1,4 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Kai_Banner" setup_version="1.0.5"/>
</config>

View File

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
<route url="V1/banner" method="GET">
<service class="Kai\Banner\Api\BannerRepositoryInterface" method="getData"/>
<resources>
<resource ref="anonymous"/>
</resources>
</route>
<route url="V1/banner" method="POST">
<service class="Kai\Banner\Api\BannerRepositoryInterface" method="postData"/>
<resources>
<resource ref="anonymous"/>
</resources>
</route>
</routes>

View File

@ -0,0 +1,6 @@
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Kai_Banner',
__DIR__
);

View File

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<page
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"
>
<body>
<referenceContainer name="content">
<block
class="Kai\Banner\Block\Adminhtml\Index"
template="Kai_Banner::index.phtml"
/>
</referenceContainer>
</body>
</page>

View File

@ -0,0 +1,299 @@
<?php
$theads = [
[
"label" => "ID",
"name" => "id",
"type" => "text",
"hidden" => false,
"disabled" => true,
],
[
"label" => "Title",
"name" => "title",
"type" => "text",
"hidden" => false,
"disabled" => false,
],
[
"label" => "HTML",
"name" => "html",
"type" => "textarea",
"hidden" => false,
"disabled" => false,
],
[
"label" => "Redirect",
"name" => "redirect",
"type" => "text",
"hidden" => false,
"disabled" => false,
],
[
"label" => "Created At",
"name" => "created_at",
"type" => "text",
"hidden" => false,
"disabled" => true,
],
[
"label" => "Updated At",
"name" => "updated_at",
"type" => "text",
"hidden" => false,
"disabled" => true,
],
[
"label" => "Status",
"name" => "status",
"type" => "text",
"hidden" => false,
"disabled" => true,
],
[
"label" => "Operation",
"type" => "text",
"hidden" => true,
"disabled" => true,
],
];
?>
<head>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div class="relative overflow-x-auto">
<caption class="px-6 flex items-start">
<button
data-action="add"
class="show-modal bg-green-700 text-white py-2 px-5 border-none"
>
Add
</button>
</caption>
<table
class="w-ful text-left rtl:text-right text-gray-7000 dark:text-gray-400"
>
<thead
class="text-gray-700 uppercase bg-gray-700 dark:bg-gray-700 dark:text-gray-400"
>
<tr>
<?php foreach ($theads as $thead): ?>
<th scope="col" class="px-6 py-3"><?=$thead['label'] ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($block->getAll() as $row): ?>
<tr
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
id="row-<?=$row['id'] ?>"
>
<th data-name="id" data-value="<?= $row['id'] ?>" class="px-6 py-4"><?=$row['id'] ?></th>
<td data-name="title" data-value="<?= $row['title'] ?>" class="px-6 py-4">
<?=$row['title'] ?>
</td>
<td data-name="html" data-value="<?= htmlentities($row['html']) ?>" class="px-6 py-4">
<code>
<?=html_entity_decode($row['html']) ?>
</code>
</td>
<td data-name="redirect" data-value="<?= $row['redirect'] ?>" class="px-6 py-4">
<?=$row['redirect'] ?>
</td>
<td data-name="created_at" data-value="<?= $row['created_at'] ?>" class="px-6 py-4">
<?=$row['created_at'] ?>
</td>
<td data-name="updated_at" data-value="<?= $row['updated_at'] ?>" class="px-6 py-4">
<?=$row['updated_at'] ?>
</td>
<td data-name="status" data-value="<?= $row['status'] ?>" class="px-6 py-4">
<?=$row['status'] ?>
</td>
<td class="px-6">
<div class="flex items-end">
<button
data-action="edit"
data-id="<?=$row['id'] ?>"
class="show-modal bg-blue-700 text-white py-2 px-5 border-none"
>
Edit
</button>
<button
data-action="delete"
data-id="<?=$row['id'] ?>"
class="show-modal bg-red-700 text-white py-2 px-5 border-none"
>
Delete
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Modal -->
<form id="modal_form" class="w-full p-5" style="display: none">
<?php foreach ($theads as $thead): ?>
<?php if (false === $thead['hidden']): ?>
<div class="flex items-center justify-center mb-6">
<div class="md:w-1/3 min-w-[20%]">
<label
class="block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4"
><?=$thead['label'] ?></label
>
</div>
<div class="md:w-2/3">
<?php if ('text' === $thead['type']): ?>
<input class="appearance-none border-2 rounded w-full py-2 px-4
text-gray-700 leading-tight focus:outline-none focus:bg-white
focus:border-purple-500" type="text" name="<?=$thead['name'] ?>"
<?=$thead['disabled'] ? 'disabled' : '' ?>
/>
<?php endif ?>
<?php if ('textarea' === $thead['type']): ?>
<textarea
rows="4"
class="appearance-none border-2 rounded w-full py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-purple-500"
name="<?=$thead['name'] ?>"
></textarea>
<?php endif ?>
</div>
</div>
<?php endif; ?>
<?php endforeach; ?>
<div class="flex items-center justify-center">
<div class="md:w-1/3 min-w-[20%]"></div>
<div class="md:w-2/3">
<button
class="shadow bg-blue-500 hover:bg-blue-400 focus:shadow-outline focus:outline-none text-white font-bold py-2 px-4 rounded"
type="submit"
>
Submit
</button>
</div>
</div>
</form>
<script>
const URL_API = location.origin + "/rest/V1/banner";
const MODAL_OPTION = {
type: "popup",
responsive: true,
innerScroll: true,
buttons: [{
class: "",
click: function() {
this.closeModal();
},
}, ],
};
require([
"jquery",
"mage/translate",
"uiComponent",
"ko",
"Magento_Ui/js/modal/alert",
"Magento_Ui/js/modal/confirm",
"Magento_Ui/js/modal/modal",
], function($, $t, Component, ko, alertModal, confirmModal, modal) {
"use strict";
(function init() {
$.ajaxSetup({
contentType: "application/json;",
headers: {
API_KEY: "<?=$block->getApiKey() ?>",
},
success: function(data) {
// do something before xhr
},
fail: function(error) {
console.error(error)
alertModal({
content: error.message,
});
},
});
})($);
$(document).ready(function($) {
const MODAL = modal(MODAL_OPTION, $("#modal_form"));
let action = "";
$(document).on("click", ".show-modal[data-action]", function() {
action = $(this).attr("data-action");
if (action == "add") {
$("#modal_form")[0].reset();
$("#modal_form").modal("openModal");
}
if (action == "edit") {
const id = $(this).attr("data-id");
$(`#row-${id} [data-name]`)
.each((i, el) => {
const name = $(el).attr('data-name');
const value = $(el).attr('data-value');
$(`#modal_form [name="${name}"]`)?.val(value);
})
$("#modal_form").modal("openModal");
}
if (action === 'delete') {
confirmModal({
title: "Are you sure?",
// content: '',
actions: {
confirm: () => {
$.get(URL_API + '?method=delete&id=' + $(this).attr('data-id'))
.then(_ => window.location.reload())
},
cancel: () => {
// do something choose "no"
},
}
})
}
});
$(document).on("submit", "#modal_form", function(e) {
e.preventDefault();
if (action === "add") {
$.post(URL_API + "?method=create", $(this).serialize())
.then((res) => {
res.status ?
alertModal({
content: "Added new record!",
actions: {
always: () => window.location.reload()
}
}) :
alertModal({
content: "Some errors: " + data.message,
});
})
.then(() => $("#modal_form").modal("closeModal"));
}
if (action === "edit") {
const id = $(this).find("[name=id").val();
$.post(
URL_API + "?method=update&id=" + id,
$("#modal_form").serialize()
)
.then((res) => {
res.status ?
alertModal({
content: "Edited new record!",
actions: {
always: () => window.location.reload()
}
}) :
alertModal({
content: "Some errors: " + data.message,
});
})
.then(() => $("#modal_form").modal("closeModal"));
}
});
});
});
</script>
</body>

View File

@ -0,0 +1,13 @@
<!-- app/code/Vendor/Module/view/frontend/layout/default.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="catalog.topnav">
<block class="Magento\Framework\View\Element\Html\Link\Current" name="kai.banner.item">
<arguments>
<argument name="label" xsi:type="string" translate="true">Kai - Banner</argument>
<argument name="path" xsi:type="string">kai/banner/index</argument>
</arguments>
</block>
</referenceBlock>
</body>
</page>

View File

@ -0,0 +1,23 @@
<?php
namespace Vendor\Module\Controller\Index;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
class Index extends Action
{
protected $resultPageFactory;
public function __construct(Context $context, PageFactory $resultPageFactory)
{
parent::__construct($context);
$this->resultPageFactory = $resultPageFactory;
}
public function execute()
{
$resultPage = $this->resultPageFactory->create();
return $resultPage;
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* module
*
* @copyright Copyright © 2023 Kai. All rights reserved.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Kai_Helloworld" setup_version="1.0.0">
</module>
</config>

View File

@ -0,0 +1,6 @@
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Kai_Helloworld',
__DIR__
);

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* routes
*
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard"> <!-- standard or admin -->
<route id="hello" frontName="hello">
<module name="Kai_Helloworld" />
</route>
</router>
</config>

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="Magento\Framework\View\Element\Template" name="kai_module.helloworld" template="Kai_Helloworld::module_index_index.phtml"/>
</referenceContainer>
</body>
</page>

View File

@ -0,0 +1 @@
<h1>Hello from Kai_Helloworld!</h1>

View File

@ -0,0 +1,27 @@
<?php
namespace Kai\Product\Block;
use Magento\Framework\Registry;
use Magento\Framework\View\Element\Template;
class Custom extends Template
{
protected $registry;
public function __construct(
\Magento\Framework\View\Element\Template\Context $context,
Registry $registry,
array $data = []
) {
parent::__construct($context, $data);
$this->registry = $registry;
}
public function getCustomAttribute()
{
$product = $this->registry->registry('current_product');
// Replace 'your_custom_attribute_code' with your actual attribute code
return $product->getData('kai_attribute');
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Kai\Product\Block\Product\View;
use Magento\Catalog\Block\Product\View\Description;
class CustomTab extends Description
{
public function headerHTML()
{
return $this->getLayout()
->createBlock("Magento\Framework\View\Element\Template")
->setTemplate("Kai_Product::product/view/header.phtml")
->toHtml();
}
public function getProductName()
{
$product = $this->getProduct();
if ($product) {
$product->getName();
}
return $product;
}
public function getImage()
{
$product = $this->getProduct();
if ($product) {
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$helperImport = $objectManager->get('\Magento\Catalog\Helper\Image');
$imageUrl = $helperImport->init($product, 'product_page_image_small')
->setImageFile($product->getSmallImage()) // image,small_image,thumbnail
->resize(380)
->getUrl();
return $imageUrl;
}
return null;
}
public function getPrice()
{
$product = $this->getProduct();
if ($product) {
return $product->getFormattedPrice();
}
return null;
}
public function getCustomAttribute()
{
$product = $this->getProduct();
// Replace 'your_custom_attribute_code' with your actual attribute code
return $product->getData('kai_attribute');
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Kai\Product\Controller\Index;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
class Index extends Action
{
protected $resultPageFactory;
public function __construct(Context $context, PageFactory $resultPageFactory)
{
parent::__construct($context);
$this->resultPageFactory = $resultPageFactory;
}
public function execute()
{
$resultPage = $this->resultPageFactory->create();
return $resultPage;
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace Kai\Product\Setup;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Setup\CategorySetupFactory;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
class InstallData implements InstallDataInterface
{
protected $eavSetupFactory;
protected $attributeSetupFactory;
public function __construct(
\Magento\Eav\Setup\EavSetupFactory $eavSetupFactory,
\Magento\Catalog\Setup\CategorySetupFactory $attributeSetupFactory)
{
$this->eavSetupFactory = $eavSetupFactory;
}
public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
$setup->startSetup();
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
$eavSetup->addAttribute(
Product::ENTITY,
'kai_attribute',
[
'type' => 'varchar',
'label' => 'Kai Attribute',
'input' => 'text',
'required' => false,
'visible' => true,
'user_defined' => true,
'searchable' => false,
'filterable' => false,
'comparable' => false,
'used_in_product_listing' => true,
'apply_to' => '',
'global' => ScopedAttributeInterface::SCOPE_GLOBAL,
'group' => 'General',
]
);
$setup->endSetup();
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/catalog_attributes.xsd">
<group name="quote_item">
<attribute name="kai_attribute"/>
</group>
<group name="wishlist_item">
<attribute name="kai_attribute" />
</group>
</config>

View File

@ -0,0 +1,7 @@
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="kai_product" frontName="kai_product">
<module name="Kai_Product"/>
</route>
</router>
</config>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* module
*
* @copyright Copyright © 2023 Kai. All rights reserved.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Kai_Product" setup_version="1.0.0">
</module>
</config>

View File

@ -0,0 +1,6 @@
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Kai_Product',
__DIR__
);

14
app/code/Kai/Product/routes.xml Executable file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* routes
*
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard"> <!-- standard or admin -->
<route id="kai_product" frontName="Kai_Product">
<module name="Kai_Product" />
</route>
</router>
</config>

View File

@ -0,0 +1,20 @@
<!-- app/code/Namespace/ModuleName/view/frontend/layout/catalog_product_view.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="product.info.details">
<block class="Kai\Product\Block\Product\View\CustomTab" name="custom.tab" template="Kai_Product::product/view/custom_tab.phtml" group="detailed_info">
<arguments>
<argument translate="true" name="title" xsi:type="string">
🛒🛒 Kai Tab
</argument>
</arguments>
</block>
</referenceBlock>
<referenceContainer name="content">
<referenceBlock name="product.info.main">
<!-- Add your custom block here -->
<block class="Kai\Product\Block\Custom" name="custom.block" template="Kai_Product::product/view/custom_attribute.phtml" after="product.info.price"/>
</referenceBlock>
</referenceContainer>
</body>
</page>

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="Magento\Framework\View\Element\Template" name="kai_module.product" template="Kai_Product::sample.phtml"/>
</referenceContainer>
</body>
</page>

View File

@ -0,0 +1,7 @@
<?php if ($block->getCustomAttribute()): ?>
<div class="custom-attribute">
<strong><?= $block->escapeHtml(__('Custom Attribute Label hello')) ?>:</strong>
<?= $block->escapeHtml($block->getCustomAttribute()) ?>
</div>
<?php endif; ?>

View File

@ -0,0 +1,22 @@
<?= $block->headerHTML() ?>
<main class="kai_product">
<div class="card">
<div class="card__img">
<picture>
<img src="<?= $block->getImage() ?>" alt="<?= $block->getProduct()->getName() ?>">
</picture>
</div>
<div class="card__content">
<h1 class="card__title"><?= $block->getProduct()->getName() ?></h1>
<p class="card__desc"><?= $block->getCustomAttribute() ?></p>
<div class="card__price">
<h1><?= $block->getPrice() ?></h1>
</div>
<button class="card__btn">
<img src="https://rvs-product-preview-card-component.vercel.app/images/icon-cart.svg" alt="">
Detail
</button>
</div>
</div>
</main>

View File

@ -0,0 +1,148 @@
<style>
:root {
--dark-cyan: hsl(158, 36%, 37%);
--cream: hsl(30, 38%, 92%);
--very-dark-blue: hsl(157, 30%, 24%);
--dark-grayish-blue: hsl(228, 12%, 48%);
--white: hsl(0, 0%, 100%);
}
.kai_product * {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.kai_product {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Montserrat', sans-serif;
}
.kai_product .card {
width: 600px;
display: flex;
box-shadow: 1px 3px 3px #ccc;
border-radius: 12px;
}
.kai_product .card .card__img {
background-color: var(--white);
border-top-left-radius: 12px;
border-bottom-left-radius: 12px;
}
.kai_product .card .card__content {
background-color: var(--cream);
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
}
.kai_product .card .card__img,
.kai_product .card .card__content {
width: 50%;
}
.kai_product .card .card__img picture {
width: 100%;
height: 100%;
display: block;
}
.kai_product .card .card__img picture img {
width: 100%;
height: 100%;
border-radius: 12px 0px 0px 12px;
object-fit: cover;
background-color: beige;
}
.kai_product .card .card__content {
padding: 30px;
display: flex;
flex-direction: column;
row-gap: 18px;
}
.kai_product .card .card__content .card__tag {
text-transform: uppercase;
letter-spacing: 3px;
font-size: 14px;
color: var(--dark-grayish-blue);
}
.kai_product .card .card__content .card__title {
font-family: 'Fraunces', serif;
text-transform: capitalize;
}
.kai_product .card .card__content .card__desc {
font-size: 14px;
color: var(--dark-grayish-blue);
line-height: 24px;
}
.kai_product .card .card__content .card__price {
display: flex;
align-items: center;
column-gap: 20px;
}
.kai_product .card .card__content .card__price h1 {
color: var(--dark-cyan);
font-family: 'Fraunces', serif;
}
.kai_product .card .card__content .card__price s {
font-size: 14px;
color: var(--dark-grayish-blue);
}
.kai_product .card .card__content .card__btn {
display: flex;
align-items: center;
justify-content: center;
column-gap: 10px;
background-color: var(--dark-cyan);
padding: 14px;
outline: none;
border: 0;
border-radius: 10px;
color: var(--white);
font-weight: 700;
font-size: 16px;
cursor: pointer;
transition: background 200ms linear;
}
.kai_product .card .card__content .card__btn:hover {
background-color: var(--very-dark-blue);
}
@media (max-width: 600px) {
.kai_product {
padding: 20px;
}
.kai_product .card {
flex-direction: column;
}
.kai_product .card .card__img,
.kai_product .card .card__content {
width: 100%;
}
.kai_product .card .card__img picture img {
border-radius: 12px 12px 0px 0px;
}
.kai_product .card .card__content {
padding: 20px;
row-gap: 16px;
}
}
</style>

View File

@ -0,0 +1 @@
<h1>Hello Product by Kai!</h1>

View File

@ -0,0 +1,10 @@
<?php
namespace Kai\Widget\Block\Widget;
use Magento\Framework\View\Element\Template;
use Magento\Widget\Block\BlockInterface;
class MyWidget extends Template implements BlockInterface
{
protected $_template = 'mywidget.phtml';
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Kai_Widget" setup_version="1.0.0"/>
</config>

View File

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<widgets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Widget:etc/widget.xsd">
<widget id="kai_widget_mywidget" class="Kai\Widget\Block\Widget\MyWidget">
<label translate="true">My Custom Widget Of Kai</label>
<description translate="true">Add widget.</description>
<parameters>
<parameter name="kai_widget__parameter_1" xsi:type="text" required="true" visible="true">
<label translate="true">Parameter 1</label>
<description translate="true">Description of the parameter 1.</description>
</parameter>
</parameters>
</widget>
</widgets>

View File

@ -0,0 +1,6 @@
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Kai_Widget',
__DIR__
);

View File

@ -0,0 +1,4 @@
<div>
<!-- Your widget content goes here -->
<p><strong>Kai Widget</strong> Hello, I am a custom widget!</p>
</div>

View File

@ -0,0 +1,11 @@
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="Magento\Widget\Block\BlockInterface" name="kai__widget_custom" template="mywidget.phtml">
<arguments>
<argument name="widget_type" xsi:type="string">vendor_module_mywidget</argument>
</arguments>
</block>
</referenceContainer>
</body>
</page>