├── Block └── System │ ├── Webhook.php │ └── Webhook │ ├── Edit.php │ └── Edit │ └── Form.php ├── Controller └── Adminhtml │ └── System │ ├── Webhook.php │ └── Webhook │ ├── Delete.php │ ├── Edit.php │ ├── Index.php │ ├── NewAction.php │ ├── Save.php │ └── Validate.php ├── LICENSE.md ├── Model ├── Observer │ ├── Customer │ │ ├── Delete.php │ │ └── Save.php │ ├── Order │ │ ├── Delete.php │ │ └── Save.php │ ├── Product │ │ ├── Delete.php │ │ └── Save.php │ └── WebhookAbstract.php ├── Resource │ ├── Webhook.php │ └── Webhook │ │ └── Collection.php └── Webhook.php ├── README.md ├── Setup └── InstallSchema.php ├── Test └── Unit │ ├── Controller │ └── Adminhtml │ │ └── System │ │ └── Webhook │ │ └── ValidateTest.php │ └── Model │ └── WebhookTest.php ├── composer.json ├── etc ├── acl.xml ├── adminhtml │ ├── menu.xml │ └── routes.xml ├── events.xml └── module.xml ├── i18n └── en_US.csv └── view └── adminhtml ├── layout ├── adminhtml_system_webhook_edit.xml ├── adminhtml_system_webhook_grid_block.xml └── adminhtml_system_webhook_index.xml ├── templates └── system │ └── webhook │ └── js.phtml └── web └── webhooks.js /Block/System/Webhook.php: -------------------------------------------------------------------------------- 1 | _blockGroup = 'SweetTooth_Webhook'; 18 | $this->_controller = 'system_webhook'; 19 | $this->_headerText = __('Webhooks'); 20 | parent::_construct(); 21 | $this->buttonList->update('add', 'label', __('Add New Webhook')); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Block/System/Webhook/Edit.php: -------------------------------------------------------------------------------- 1 | _coreRegistry = $registry; 28 | parent::__construct($context, $data); 29 | } 30 | 31 | /** 32 | * Internal constructor 33 | * 34 | * @return void 35 | */ 36 | protected function _construct() 37 | { 38 | $this->_objectId = 'webhook_id'; 39 | $this->_blockGroup = 'SweetTooth_Webhook'; 40 | $this->_controller = 'system_webhook'; 41 | 42 | parent::_construct(); 43 | } 44 | 45 | /** 46 | * Getter 47 | * 48 | * @return \SweetTooth\Webhook\Model\Webhook 49 | */ 50 | public function getWebhook() 51 | { 52 | return $this->_coreRegistry->registry('current_webhook'); 53 | } 54 | 55 | /** 56 | * Prepare layout. 57 | * Adding save_and_continue button 58 | * 59 | * @return $this 60 | */ 61 | protected function _preparelayout() 62 | { 63 | $this->addButton( 64 | 'save_and_edit', 65 | [ 66 | 'label' => __('Save and Continue Edit'), 67 | 'class' => 'save', 68 | 'data_attribute' => [ 69 | 'mage-init' => ['button' => ['event' => 'saveAndContinueEdit', 'target' => '#edit_form']], 70 | ] 71 | ], 72 | 100 73 | ); 74 | if (!$this->getWebhook()->getId()) { 75 | $this->removeButton('delete'); 76 | } 77 | return parent::_prepareLayout(); 78 | } 79 | 80 | /** 81 | * Return form HTML 82 | * 83 | * @return string 84 | */ 85 | public function getFormHtml() 86 | { 87 | $formHtml = parent::getFormHtml(); 88 | if (!$this->_storeManager->isSingleStoreMode() && $this->getWebhook()->getId()) { 89 | $formHtml = $formHtml; 90 | } 91 | return $formHtml; 92 | } 93 | 94 | /** 95 | * Return translated header text depending on creating/editing action 96 | * 97 | * @return \Magento\Framework\Phrase 98 | */ 99 | public function getHeaderText() 100 | { 101 | if ($this->getWebhook()->getId()) { 102 | return __('Webhook "%1"', $this->escapeHtml($this->getWebhook()->getName())); 103 | } else { 104 | return __('New Webhook'); 105 | } 106 | } 107 | 108 | /** 109 | * Return validation url for edit form 110 | * 111 | * @return string 112 | */ 113 | public function getValidationUrl() 114 | { 115 | return $this->getUrl('adminhtml/*/validate', ['_current' => true]); 116 | } 117 | 118 | /** 119 | * Return save url for edit form 120 | * 121 | * @return string 122 | */ 123 | public function getSaveUrl() 124 | { 125 | return $this->getUrl('adminhtml/*/save', ['_current' => true, 'back' => null]); 126 | } 127 | 128 | /** 129 | * Return save and continue url for edit form 130 | * 131 | * @return string 132 | */ 133 | public function getSaveAndContinueUrl() 134 | { 135 | return $this->getUrl('adminhtml/*/save', ['_current' => true, 'back' => 'edit']); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Block/System/Webhook/Edit/Form.php: -------------------------------------------------------------------------------- 1 | _coreRegistry->registry('current_webhook'); 18 | } 19 | 20 | /** 21 | * Prepare form before rendering HTML 22 | * 23 | * @return \SweetTooth\Webhook\Block\System\Webhook\Edit\Form 24 | */ 25 | protected function _prepareForm() 26 | { 27 | /** @var \Magento\Framework\Data\Form $form */ 28 | $form = $this->_formFactory->create( 29 | ['data' => ['id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post']] 30 | ); 31 | 32 | $fieldset = $form->addFieldset('base', ['legend' => __('Webhook'), 'class' => 'fieldset-wide']); 33 | 34 | $fieldset->addField( 35 | 'event', 36 | 'select', 37 | [ 38 | 'name' => 'event', 39 | 'label' => __('Event'), 40 | 'title' => __('Event'), 41 | 'required' => true, 42 | 'class' => 'validate-xml-identifier', 43 | 44 | /** 45 | * TODO: We should build this in some kind of dynamic way 46 | * through config or at the very least move this options array 47 | * to a shared location so it can be used elsewhere 48 | */ 49 | 'options' => [ 50 | 'customer/saved' => 'Customer Updated', 51 | 'customer/deleted' => 'Customer Deleted', 52 | 'product/saved' => 'Product Updated', 53 | 'product/deleted' => 'Product Deleted', 54 | 'order/saved' => 'Order Updated', 55 | 'order/deleted' => 'Order Deleted', 56 | ] 57 | ] 58 | ); 59 | 60 | $fieldset->addField( 61 | 'url', 62 | 'text', 63 | ['name' => 'url', 'label' => __('Url'), 'title' => __('Url'), 'required' => true] 64 | ); 65 | 66 | $form->setValues($this->getWebhook()->getData())->addFieldNameSuffix('webhook')->setUseContainer(true); 67 | 68 | $this->setForm($form); 69 | return parent::_prepareForm(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Controller/Adminhtml/System/Webhook.php: -------------------------------------------------------------------------------- 1 | _coreRegistry = $coreRegistry; 56 | parent::__construct($context); 57 | $this->resultForwardFactory = $resultForwardFactory; 58 | $this->resultJsonFactory = $resultJsonFactory; 59 | $this->resultPageFactory = $resultPageFactory; 60 | $this->layoutFactory = $layoutFactory; 61 | } 62 | 63 | /** 64 | * Initialize Layout and set breadcrumbs 65 | * 66 | * @return \Magento\Backend\Model\View\Result\Page 67 | */ 68 | protected function createPage() 69 | { 70 | /** @var \Magento\Backend\Model\View\Result\Page $resultPage */ 71 | $resultPage = $this->resultPageFactory->create(); 72 | $resultPage->setActiveMenu('SweetTooth_Webhook::system_webhook') 73 | ->addBreadcrumb(__('Webhooks'), __('Webhooks')); 74 | return $resultPage; 75 | } 76 | 77 | /** 78 | * Initialize Webhook object 79 | * 80 | * @return \SweetTooth\Webhook\Model\Webhook 81 | */ 82 | protected function _initWebhook() 83 | { 84 | $webhookId = $this->getRequest()->getParam('webhook_id', null); 85 | $storeId = (int)$this->getRequest()->getParam('store', 0); 86 | /* @var $webhook \SweetTooth\Webhook\Model\Webhook */ 87 | $webhook = $this->_objectManager->create('SweetTooth\Webhook\Model\Webhook'); 88 | if ($webhookId) { 89 | $webhook->setStoreId($storeId)->load($webhookId); 90 | } 91 | $this->_coreRegistry->register('current_webhook', $webhook); 92 | return $webhook; 93 | } 94 | 95 | /** 96 | * Check current user permission 97 | * 98 | * @return bool 99 | */ 100 | protected function _isAllowed() 101 | { 102 | return $this->_authorization->isAllowed('SweetTooth_Webhook::webhook'); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Controller/Adminhtml/System/Webhook/Delete.php: -------------------------------------------------------------------------------- 1 | _initWebhook(); 15 | /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ 16 | $resultRedirect = $this->resultRedirectFactory->create(); 17 | if ($webhook->getId()) { 18 | try { 19 | $webhook->delete(); 20 | $this->messageManager->addSuccess(__('You deleted the webhook.')); 21 | } catch (\Exception $e) { 22 | $this->messageManager->addError($e->getMessage()); 23 | return $resultRedirect->setPath('adminhtml/*/edit', ['_current' => true]); 24 | } 25 | } 26 | return $resultRedirect->setPath('adminhtml/*/'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Controller/Adminhtml/System/Webhook/Edit.php: -------------------------------------------------------------------------------- 1 | _initWebhook(); 15 | 16 | $resultPage = $this->createPage(); 17 | $resultPage->getConfig()->getTitle()->prepend(__('Webhooks')); 18 | $resultPage->getConfig()->getTitle()->prepend( 19 | $webhook->getId() ? $webhook->getCode() : __('New Webhook') 20 | ); 21 | $resultPage->addContent($resultPage->getLayout()->createBlock('SweetTooth\Webhook\Block\System\Webhook\Edit')) 22 | ->addJs( 23 | $resultPage->getLayout()->createBlock( 24 | 'Magento\Framework\View\Element\Template', 25 | '', 26 | ['data' => ['template' => 'SweetTooth_Webhook::system/webhook/js.phtml']] 27 | ) 28 | ); 29 | return $resultPage; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Controller/Adminhtml/System/Webhook/Index.php: -------------------------------------------------------------------------------- 1 | createPage(); 15 | $resultPage->getConfig()->getTitle()->prepend(__('Webhooks')); 16 | return $resultPage; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Controller/Adminhtml/System/Webhook/NewAction.php: -------------------------------------------------------------------------------- 1 | resultForwardFactory->create(); 16 | return $resultForward->forward('edit'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Controller/Adminhtml/System/Webhook/Save.php: -------------------------------------------------------------------------------- 1 | _initWebhook(); 15 | $data = $this->getRequest()->getPost('webhook'); 16 | $back = $this->getRequest()->getParam('back', false); 17 | /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ 18 | $resultRedirect = $this->resultRedirectFactory->create(); 19 | if ($data) { 20 | $data['webhook_id'] = $webhook->getId(); 21 | $webhook->setData($data); 22 | try { 23 | $webhook->save(); 24 | $this->messageManager->addSuccess(__('You saved the webhook.')); 25 | if ($back) { 26 | $resultRedirect->setPath( 27 | 'adminhtml/*/edit', 28 | ['_current' => true, 'webhook_id' => $webhook->getId()] 29 | ); 30 | } else { 31 | $resultRedirect->setPath('adminhtml/*/'); 32 | } 33 | return $resultRedirect; 34 | } catch (\Exception $e) { 35 | $this->messageManager->addError($e->getMessage()); 36 | return $resultRedirect->setPath('adminhtml/*/edit', ['_current' => true]); 37 | } 38 | } 39 | return $resultRedirect->setPath('adminhtml/*/'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Controller/Adminhtml/System/Webhook/Validate.php: -------------------------------------------------------------------------------- 1 | false]); 15 | $webhook = $this->_initWebhook(); 16 | $webhook->addData($this->getRequest()->getPost('webhook')); 17 | $result = $webhook->validate(); 18 | if ($result instanceof \Magento\Framework\Phrase) { 19 | $this->messageManager->addError($result->getText()); 20 | $layout = $this->layoutFactory->create(); 21 | $layout->initMessages(); 22 | $response->setError(true); 23 | $response->setHtmlMessage($layout->getMessagesBlock()->getGroupedHtml()); 24 | } 25 | /** @var \Magento\Framework\Controller\Result\Json $resultJson */ 26 | $resultJson = $this->resultJsonFactory->create(); 27 | return $resultJson->setData($response->toArray()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sweet Tooth Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Model/Observer/Customer/Delete.php: -------------------------------------------------------------------------------- 1 | getEvent()->getCustomer(); 20 | 21 | /** 22 | * TODO: Add some type of serialization which filters the 23 | * actual fields that get returned from the object. Returning 24 | * this raw data is dangerous and can expose sensitive data. 25 | * 26 | * Ideally this representation of the object will match that 27 | * of the json rest api. Maybe we can tap into that serializer? 28 | */ 29 | return [ 30 | 'customer' => $customer->getData() 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Model/Observer/Customer/Save.php: -------------------------------------------------------------------------------- 1 | getEvent()->getCustomer(); 20 | 21 | /** 22 | * TODO: Add some type of serialization which filters the 23 | * actual fields that get returned from the object. Returning 24 | * this raw data is dangerous and can expose sensitive data. 25 | * 26 | * Ideally this representation of the object will match that 27 | * of the json rest api. Maybe we can tap into that serializer? 28 | */ 29 | return [ 30 | 'customer' => $customer->getData() 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Model/Observer/Order/Delete.php: -------------------------------------------------------------------------------- 1 | getEvent()->getOrder(); 20 | 21 | /** 22 | * TODO: Add some type of serialization which filters the 23 | * actual fields that get returned from the object. Returning 24 | * this raw data is dangerous and can expose sensitive data. 25 | * 26 | * Ideally this representation of the object will match that 27 | * of the json rest api. Maybe we can tap into that serializer? 28 | */ 29 | return [ 30 | 'order' => $order->getData() 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Model/Observer/Order/Save.php: -------------------------------------------------------------------------------- 1 | getEvent()->getOrder(); 20 | 21 | /** 22 | * TODO: Add some type of serialization which filters the 23 | * actual fields that get returned from the object. Returning 24 | * this raw data is dangerous and can expose sensitive data. 25 | * 26 | * Ideally this representation of the object will match that 27 | * of the json rest api. Maybe we can tap into that serializer? 28 | */ 29 | return [ 30 | 'order' => $order->getData() 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Model/Observer/Product/Delete.php: -------------------------------------------------------------------------------- 1 | getEvent()->getProduct(); 20 | 21 | /** 22 | * TODO: Add some type of serialization which filters the 23 | * actual fields that get returned from the object. Returning 24 | * this raw data is dangerous and can expose sensitive data. 25 | * 26 | * Ideally this representation of the object will match that 27 | * of the json rest api. Maybe we can tap into that serializer? 28 | */ 29 | return [ 30 | 'product' => $product->getData() 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Model/Observer/Product/Save.php: -------------------------------------------------------------------------------- 1 | getEvent()->getProduct(); 20 | 21 | /** 22 | * TODO: Add some type of serialization which filters the 23 | * actual fields that get returned from the object. Returning 24 | * this raw data is dangerous and can expose sensitive data. 25 | * 26 | * Ideally this representation of the object will match that 27 | * of the json rest api. Maybe we can tap into that serializer? 28 | */ 29 | return [ 30 | 'product' => $product->getData() 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Model/Observer/WebhookAbstract.php: -------------------------------------------------------------------------------- 1 | _logger = $logger; 44 | $this->_curlAdapter = $curlAdapter; 45 | $this->_jsonHelper = $jsonHelper; 46 | $this->_webhookFactory = $webhookFactory; 47 | } 48 | 49 | /** 50 | * Set new customer group to all his quotes 51 | * 52 | * @param Observer $observer 53 | * @return void 54 | */ 55 | public function dispatch(Observer $observer) 56 | { 57 | $eventCode = $this->_getWebhookEvent(); 58 | $eventData = $this->_getWebhookData($observer); 59 | 60 | $body = [ 61 | 'event' => $eventCode, 62 | 'data' => $eventData 63 | ]; 64 | 65 | $webhooks = $this->_webhookFactory 66 | ->create() 67 | ->getCollection() 68 | ->addFieldToFilter('event', $eventCode); 69 | 70 | foreach($webhooks as $webhook) 71 | { 72 | $this->_sendWebhook($webhook->getUrl(), $body); 73 | } 74 | } 75 | 76 | protected function _sendWebhook($url, $body) 77 | { 78 | $this->_logger->debug("Sending webhook for event " . $this->_getWebhookEvent() . " to " . $url); 79 | 80 | $bodyJson = $this->_jsonHelper->jsonEncode($body); 81 | 82 | $headers = ["Content-Type: application/json"]; 83 | $this->_curlAdapter->write('POST', $url, '1.1', $headers, $bodyJson); 84 | $this->_curlAdapter->read(); 85 | $this->_curlAdapter->close(); 86 | } 87 | 88 | protected function _getWebhookEvent() 89 | { 90 | // TODO: Throw here because this is an abstract function 91 | return false; 92 | } 93 | 94 | protected function _getWebhookData(Observer $observer) 95 | { 96 | // TODO: Throw here because this is an abstract function 97 | return false; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Model/Resource/Webhook.php: -------------------------------------------------------------------------------- 1 | _init('webhook', 'webhook_id'); 18 | } 19 | 20 | /** 21 | * Load webhook by code 22 | * 23 | * @param \SweetTooth\Webhook\Model\Webhook $object 24 | * @param string $code 25 | * @return $this 26 | */ 27 | public function loadByCode(\SweetTooth\Webhook\Model\Webhook $object, $code) 28 | { 29 | if ($result = $this->getWebhookByCode($code, true, $object->getStoreId())) { 30 | $object->setData($result); 31 | } 32 | return $this; 33 | } 34 | 35 | /** 36 | * Retrieve webhook data by code 37 | * 38 | * @param string $code 39 | * @param bool $withValue 40 | * @param integer $storeId 41 | * @return array 42 | */ 43 | public function getWebhookByCode($code, $withValue = false, $storeId = 0) 44 | { 45 | $select = $this->_getReadAdapter()->select()->from( 46 | $this->getMainTable() 47 | )->where( 48 | $this->getMainTable() . '.code = ?', 49 | $code 50 | ); 51 | return $this->_getReadAdapter()->fetchRow($select); 52 | } 53 | 54 | /** 55 | * Perform actions after object save 56 | * 57 | * @param \Magento\Framework\Model\AbstractModel $object 58 | * @return $this 59 | */ 60 | protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) 61 | { 62 | parent::_afterSave($object); 63 | return $this; 64 | } 65 | 66 | /** 67 | * Retrieve select object for load object data 68 | * 69 | * @param string $field 70 | * @param mixed $value 71 | * @param \Magento\Framework\Model\AbstractModel $object 72 | * @return $this 73 | */ 74 | protected function _getLoadSelect($field, $value, $object) 75 | { 76 | $select = parent::_getLoadSelect($field, $value, $object); 77 | return $select; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Model/Resource/Webhook/Collection.php: -------------------------------------------------------------------------------- 1 | _init('SweetTooth\Webhook\Model\Webhook', 'SweetTooth\Webhook\Model\Resource\Webhook'); 26 | } 27 | 28 | /** 29 | * Setter 30 | * 31 | * @param integer $storeId 32 | * @return $this 33 | */ 34 | public function setStoreId($storeId) 35 | { 36 | $this->_storeId = $storeId; 37 | return $this; 38 | } 39 | 40 | /** 41 | * Getter 42 | * 43 | * @return integer 44 | */ 45 | public function getStoreId() 46 | { 47 | return $this->_storeId; 48 | } 49 | 50 | /** 51 | * Add store values to result 52 | * 53 | * @return $this 54 | */ 55 | public function addValuesToResult() 56 | { 57 | return $this; 58 | } 59 | 60 | /** 61 | * Retrieve option array 62 | * 63 | * @return array 64 | */ 65 | public function toOptionArray() 66 | { 67 | return $this->_toOptionArray('code', 'name'); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Model/Webhook.php: -------------------------------------------------------------------------------- 1 | _escaper = $escaper; 48 | parent::__construct($context, $registry, $resource, $resourceCollection, $data); 49 | } 50 | 51 | /** 52 | * Internal Constructor 53 | * 54 | * @return void 55 | */ 56 | protected function _construct() 57 | { 58 | parent::_construct(); 59 | $this->_init('SweetTooth\Webhook\Model\Resource\Webhook'); 60 | } 61 | 62 | /** 63 | * Setter 64 | * 65 | * @param integer $storeId 66 | * @return $this 67 | * @codeCoverageIgnore 68 | */ 69 | public function setStoreId($storeId) 70 | { 71 | $this->_storeId = $storeId; 72 | return $this; 73 | } 74 | 75 | /** 76 | * Getter 77 | * 78 | * @return integer 79 | * @codeCoverageIgnore 80 | */ 81 | public function getStoreId() 82 | { 83 | return $this->_storeId; 84 | } 85 | 86 | /** 87 | * Load webhook by code 88 | * 89 | * @param string $code 90 | * @return $this 91 | * @codeCoverageIgnore 92 | */ 93 | public function loadByCode($code) 94 | { 95 | $this->getResource()->loadByCode($this, $code); 96 | return $this; 97 | } 98 | 99 | /** 100 | * Return webhook value depend on given type 101 | * 102 | * @param string $type 103 | * @return string 104 | */ 105 | public function getValue($type = null) 106 | { 107 | if ($type === null) { 108 | $type = self::TYPE_HTML; 109 | } 110 | if ($type == self::TYPE_TEXT || !strlen((string)$this->getData('html_value'))) { 111 | $value = $this->getData('plain_value'); 112 | //escape html if type is html, but html value is not defined 113 | if ($type == self::TYPE_HTML) { 114 | $value = nl2br($this->_escaper->escapeHtml($value)); 115 | } 116 | return $value; 117 | } 118 | return $this->getData('html_value'); 119 | } 120 | 121 | /** 122 | * Validation of object data 123 | * 124 | * @return \Magento\Framework\Phrase|bool 125 | */ 126 | public function validate() 127 | { 128 | return true; 129 | } 130 | 131 | /** 132 | * Retrieve webhooks option array 133 | * @todo: extract method as separate class 134 | * @param bool $withGroup 135 | * @return array 136 | */ 137 | public function getWebhooksOptionArray($withGroup = false) 138 | { 139 | /* @var $collection \SweetTooth\Webhook\Model\Resource\Webhook\Collection */ 140 | $collection = $this->getCollection(); 141 | $webhooks = []; 142 | foreach ($collection->toOptionArray() as $webhook) { 143 | $webhooks[] = [ 144 | 'value' => '{{customVar code=' . $webhook['value'] . '}}', 145 | 'label' => __('%1', $webhook['label']), 146 | ]; 147 | } 148 | if ($withGroup && $webhooks) { 149 | $webhooks = ['label' => __('Webhooks'), 'value' => $webhooks]; 150 | } 151 | return $webhooks; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Note:** This is alpha software and not to be used in production until v1.0.0 2 | 3 | # Webhooks for Magento 2 4 | 5 | ![Preview](https://s3.amazonaws.com/sweettooth-static/github/magento2-webhook-preview) 6 | 7 | This module provides webhooks for Magento 2 events. Inspired by [Alan Kent's](https://twitter.com/akent99)'s [Webhooks in Magento 2](http://alankent.me/2015/05/13/webhooks-in-magento-2/) blog post which mentions a rough timeline for getting webhooks in Magento 2 core: 8 | 9 | > Better support for webhooks is on the backlog, but currently not guaranteed for Magento 2 GA. 10 | 11 | We're hopeful the community can help push this effort forward through this module. 12 | 13 | ## Getting Started 14 | 15 | Install via composer 16 | ``` 17 | composer require sweettooth/magento2-module-webhook 18 | ``` 19 | 20 | Add `SweetTooth_Webhook` to your `app/etc/config.php` 21 | ```php 22 | 25 | array ( 26 | // 27 | // Bunch of other modules 28 | // 29 | 'SweetTooth_Webhook' => 1, 30 | ), 31 | ); 32 | ``` 33 | 34 | Run database migrations 35 | ``` 36 | php bin/magento setup:upgrade 37 | ``` 38 | 39 | ## Supported Webhooks 40 | 41 | Available now 42 | - Customer updated 43 | - Customer deleted 44 | - Order updated 45 | - Order deleted 46 | - Product updated 47 | - Product deleted 48 | 49 | TODO 50 | - Customer created 51 | - Order created 52 | - Product created 53 | - CRUD operations for other resources 54 | 55 | ## Roadmap and areas for discussion 56 | 57 | ### Async webhooks [(RFC)](https://github.com/sweettooth/magento2-module-webhook/issues/6) 58 | Without async webhooks, this module is pretty much a no-go for production shops - the dependency on third party systems is just too risky to do synchronously. The best practice for performing tasks asynchronously is to queue it up on a memory store (redis, memcache, etc) and have a background worker pick up the job and perform it, meanwhile the synchronous request returns immediately. Since there's no native queueing for Magento 2, our best bet might be to use the database as a 'queue' ("Blasphemy!" you say. Chill, magento already does this in the newsletter module) then use the cron to pick up the jobs every minute. 59 | 60 | ### Serialization [(RFC)](https://github.com/sweettooth/magento2-module-webhook/issues/7) 61 | Right now serializing the payload of the webhook is super basic, just calling `getData()` on the model. This is all kinds of bad because it will expose sensitive information like password hashes and such. A better strategy would be to create a serializer for each resource. An even better strategy is if we could re-use the serializer for the REST API so our webhook data has an identical json structure to the API. Boom. 62 | 63 | ### Extensibility 64 | It would be really cool to make this module extendible so other modules could add events that can be webhook'd 65 | 66 | ### Creating webhooks through the API 67 | This is a really legit use case. An app that has API access to a shop may want to register webhooks to receive CRUD events on specific resources that they would otherwise need to poll for every x hours. Both Shopify and Bigcommerce have this endpoint and it's lovely. 68 | 69 | ### Data formats 70 | We should probably support XML someday. *sigh* 71 | 72 | ## Contributing 73 | 74 | Submit a pull request! 75 | -------------------------------------------------------------------------------- /Setup/InstallSchema.php: -------------------------------------------------------------------------------- 1 | getConnection(); 23 | 24 | $installer->startSetup(); 25 | 26 | /** 27 | * Create table 'webhook' 28 | */ 29 | $webhookTableName = $installer->getTable('webhook'); 30 | 31 | $table = $connection->newTable( 32 | $webhookTableName 33 | )->addColumn( 34 | 'webhook_id', 35 | \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, 36 | null, 37 | ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], 38 | 'Webhook Id' 39 | )->addColumn( 40 | 'event', 41 | \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 42 | 255, 43 | [], 44 | 'Event' 45 | )->addColumn( 46 | 'url', 47 | \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 48 | 255, 49 | [], 50 | 'Url' 51 | )->setComment( 52 | 'Webhooks' 53 | ); 54 | $connection->createTable($table); 55 | 56 | $installer->endSetup(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Test/Unit/Controller/Adminhtml/System/Webhook/ValidateTest.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /etc/adminhtml/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /etc/adminhtml/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /etc/events.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /i18n/en_US.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smile-museum/magento2-module-webhook/7d26af32c205eef72519d941cd0b3df984581b9e/i18n/en_US.csv -------------------------------------------------------------------------------- /view/adminhtml/layout/adminhtml_system_webhook_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /view/adminhtml/layout/adminhtml_system_webhook_grid_block.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | webhooksGrid 8 | SweetTooth\Webhook\Model\Resource\Webhook\Collection 9 | webhook_id 10 | ASC 11 | 12 | 13 | 14 | 15 | adminhtml/*/edit 16 | 17 | getId 18 | 19 | 20 | 21 | 22 | 23 | Webhook ID 24 | webhook_id 25 | col-id 26 | col-id 27 | 28 | 29 | 30 | 31 | Event 32 | event 33 | 34 | 35 | 36 | 37 | Url 38 | url 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /view/adminhtml/layout/adminhtml_system_webhook_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /view/adminhtml/templates/system/webhook/js.phtml: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /view/adminhtml/web/webhooks.js: -------------------------------------------------------------------------------- 1 | // Nothing here yet --------------------------------------------------------------------------------