diff --git a/.github/ISSUE_TEMPLATE/relatorio-de-bug.md b/.github/ISSUE_TEMPLATE/relatorio-de-bug.md new file mode 100644 index 0000000..c4fcc0d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/relatorio-de-bug.md @@ -0,0 +1,34 @@ +--- +name: Relatorio de bug +about: Forneça informações do bug encontrado +--- + +_Este relatório deve ser usado **APENAS** para reportar bugs_ + +## Comportamento esperado + + + +## Comportamento atual + + + +## Ambiente (produção, sandbox) + +## Passos para reproduzir o bug + + + + +1. +2. +3. +4. + +## Descrição Detalhada + + + +## Possível solução + + diff --git a/.github/workflows/codacy-analysis.yml b/.github/workflows/codacy-analysis.yml new file mode 100644 index 0000000..7903623 --- /dev/null +++ b/.github/workflows/codacy-analysis.yml @@ -0,0 +1,46 @@ +# This workflow checks out code, performs a Codacy security scan +# and integrates the results with the +# GitHub Advanced Security code scanning feature. For more information on +# the Codacy security scan action usage and parameters, see +# https://github.com/codacy/codacy-analysis-cli-action. +# For more information on Codacy Analysis CLI in general, see +# https://github.com/codacy/codacy-analysis-cli. + +name: Codacy Security Scan + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + codacy-security-scan: + name: Codacy Security Scan + runs-on: ubuntu-latest + steps: + # Checkout the repository to the GitHub Actions runner + - name: Checkout code + uses: actions/checkout@v2 + + # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis + - name: Run Codacy Analysis CLI + uses: codacy/codacy-analysis-cli-action@1.1.0 + with: + # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository + # You can also omit the token and run the tools that support default configurations + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + verbose: true + output: results.sarif + format: sarif + # Adjust severity of non-security issues + gh-code-scanning-compat: true + # Force 0 exit code to allow SARIF file generation + # This will handover control about PR rejection to the GitHub side + max-allowed-issues: 2147483647 + + # Upload the SARIF file generated in the previous step + - name: Upload SARIF results file + uses: github/codeql-action/upload-sarif@v1 + with: + sarif_file: results.sarif \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..8c6547d --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,62 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +name: "CodeQL" + +on: + push: + branches: [master] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '0 3 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['javascript'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.gitignore b/.gitignore index e2ea013..65f8937 100644 --- a/.gitignore +++ b/.gitignore @@ -124,3 +124,27 @@ skin/frontend/default/modern/ skin/frontend/enterprise skin/install/ var/ +#prettierconfgs +/.cache +/node_modules +/scripts/release/node_modules +*.log +/errors +/test*.* +/.vscode +/dist +/website/node_modules +/website/build +/website/i18n +/website/static/playground.js +/website/static/lib +.DS_Store +/coverage +.idea +package-lock.json +.yarn/* +!.yarn/releases +!.yarn/plugins +!.yarn/sdks +!.yarn/versions +.pnp.* \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000..41d877d Binary files /dev/null and b/.vs/slnx.sqlite differ diff --git a/Block/Product/View/Installments.php b/Block/Product/View/Installments.php index b2f3cb7..0599cd0 100644 --- a/Block/Product/View/Installments.php +++ b/Block/Product/View/Installments.php @@ -88,6 +88,6 @@ public function getInstallment($value) */ public function isEnabled() { $status = $this->_scopeConfig->getValue('payment/pagseguro/installments'); - return (! is_null($status) && $status == 1) ? true : false; + return (! ($status=== null) && $status=== 1) ? true : false; } } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..054249c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,52 @@ +1.12.0 + +- Checkout sem endereço (para produtos do tipo 'virtual' e 'downloadable') +- Habilitar/desabilitar recuperação de carrinho do PagSeguro via admin +- Tela de listar transações no admin, permitindo ver detalhes da transação +- Estorno parcial +- Disconto por meio de pagamento via configuração do módulo (admin) para o checkout padrão/lightbox +- Atualizada versão do pagseguro-php-sdk no composer.json para utilizar as versões 4.+ +- Adicionada compatibilidade com endereços de 4 linhas, no formato: 1 rua/endereço, 2 número, 3 complemento, 4 bairro (padrão brasileiro) +- Valida se o telefone do comprador foi configurado antes de tentar usar o telefone do endereço de entrega +- Fix: Corrigido id dos itens do pedido (carrinho) enviados para o PagSeguro + + 1.4.0 + +- Alterado o fluxo do checkout transparente (na própria tela de checkout do Magento) +- Alterada a forma de configurar o módulo e os meios de pagamento do PagSeguro, que agora são configurados individualmente. +- Melhorias gerais e correções de bugs: transações do admin, css muito abrangente, remoção de arquivos velhos e desnecessários, refatorações. + + 1.3.0 + +- Adicionada validação e mensagens de erro (frontend) nos formulários do checkout transparente + + 1.2.6 + +- Melhoria na configuração do log na interface administrativa +- Adicionada seção de atualização do módulo e atualização geral da documentação (README.md) +- Correção de bugs quando o pedido deixava de existir ou a sessão era encerrada +- Correçao para aceitar CVV de 4 digitos +- Melhoria no acesso aos dados do endereço do cliente + + 1.2.1 + +- Alterada a biblioteca JavaScript utilizada nas máscaras. + + 1.2.0 + +- Adicionada opção para utilizar o Checkout Transparente. + + 1.1.0 + +- Possibilidade de consultar e solicitar o cancelamento de transações; +- Possibilidade de consultar e solicitar o estorno de transações; +- Possibilidade de definir descontos com base no meio de pagamento escolhido durante o checkout PagSeguro; + + 1.0.0 + +- Adicionando opção para utilização do Checkout Lightbox. +- Integração com API de Notificação. +- Integração com API de Pagamento do PagSeguro. +- Configuração do Setup do módulo. +- Adicionado meio de pagamento ao Magento2 +- Versão inicial. diff --git a/Controller/Adminhtml/Refund/Refund.php b/Controller/Adminhtml/Refund/Refund.php index 0d98f65..f5c0897 100755 --- a/Controller/Adminhtml/Refund/Refund.php +++ b/Controller/Adminhtml/Refund/Refund.php @@ -62,9 +62,8 @@ public function execute() $this->_objectManager->create('UOL\PagSeguro\Helper\Library'), $this->_objectManager->create('UOL\PagSeguro\Helper\Crypt') ); - try { - return $this->whenSuccess($refund->execute($this->getRequest()->getParam('data'))); + return $this->whenSuccess($refund->execute($this->getRequest()->getParam('data'), $this->getRequest()->getParam('value'))); } catch (\Exception $exception) { return $this->whenError($exception->getMessage()); } diff --git a/Controller/Adminhtml/Transactions/Index.php b/Controller/Adminhtml/Transactions/Index.php new file mode 100755 index 0000000..a63b561 --- /dev/null +++ b/Controller/Adminhtml/Transactions/Index.php @@ -0,0 +1,73 @@ +_objectManager->create('UOL\PagSeguro\Helper\Auth'); + + /** Check for credentials **/ + if (!$authHelper->hasCredentials()) + return $this->_redirect('pagseguro/credentials/error'); + + /** @var \Magento\Backend\Model\View\Result\Page $resultPage */ + $resultPage = $this->_resultPageFactory->create(); + $resultPage->getConfig()->getTitle()->prepend(__('Listar transações')); + $resultPage->getLayout()->getBlock('adminhtml.block.pagseguro.transactions.content')->setData('adminurl', $this->getAdminUrl()); + return $resultPage; + } + + /** + * Cancellation access rights checking + * + * @return bool + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('UOL_PagSeguro::Transactions'); + } +} diff --git a/Controller/Adminhtml/Transactions/Request.php b/Controller/Adminhtml/Transactions/Request.php new file mode 100755 index 0000000..0583419 --- /dev/null +++ b/Controller/Adminhtml/Transactions/Request.php @@ -0,0 +1,85 @@ +_objectManager->create('Magento\Framework\App\Config\ScopeConfigInterface'), + $this->_objectManager->create('Magento\Framework\App\ResourceConnection'), + $this->_objectManager->create('Magento\Framework\Model\ResourceModel\Db\Context'), + $this->_objectManager->create('Magento\Backend\Model\Session'), + $this->_objectManager->create('Magento\Sales\Model\Order'), + $this->_objectManager->create('UOL\PagSeguro\Helper\Library'), + $this->_objectManager->create('UOL\PagSeguro\Helper\Crypt'), + $this->getRequest()->getParam('id_magento'), + $this->getRequest()->getParam('id_pagseguro'), + $this->getRequest()->getParam('date_begin'), + $this->getRequest()->getParam('date_end'), + $this->getRequest()->getParam('status') + ); + + try { + return $this->whenSuccess($transactions->request()); + } catch (\Exception $exception) { + return $this->whenError($exception->getMessage()); + } + } + + /** + * Cancellation access rights checking + * + * @return bool + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('UOL_PagSeguro::Transactions'); + } +} diff --git a/Controller/Adminhtml/Transactions/Transaction.php b/Controller/Adminhtml/Transactions/Transaction.php new file mode 100755 index 0000000..713d7d5 --- /dev/null +++ b/Controller/Adminhtml/Transactions/Transaction.php @@ -0,0 +1,77 @@ +transactionsFactory = $transactionsFactory; + parent::__construct($context, $resultJsonFactory); + } + + /** + * @return \Magento\Framework\Controller\Result\Json + */ + public function execute() + { + $transactions = $this->transactionsFactory->create(); + + try { + return $this->whenSuccess( + $transactions->execute( + $this->getRequest()->getParam('transaction') ) + ); + } catch (\Exception $exception) { + return $this->whenError($exception->getMessage()); + } + } + + /** + * Transactions access rights checking + * + * @return bool + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('UOL_PagSeguro::Transactions'); + } +} \ No newline at end of file diff --git a/Controller/Direct/Boleto.php b/Controller/Direct/Boleto.php index 27b3ed6..bbd01dd 100644 --- a/Controller/Direct/Boleto.php +++ b/Controller/Direct/Boleto.php @@ -197,7 +197,7 @@ private function lastRealOrderId() { $lastRealOrderId = $this->_objectManager->create('\Magento\Checkout\Model\Session')->getLastRealOrder()->getId(); - if (is_null($lastRealOrderId)) { + if ($lastRealOrderId) { throw new \Exception("There is no order associated with this session."); } diff --git a/Controller/Direct/Debit.php b/Controller/Direct/Debit.php index 76e958c..9c0ba57 100755 --- a/Controller/Direct/Debit.php +++ b/Controller/Direct/Debit.php @@ -234,7 +234,7 @@ private function lastRealOrderId() { $lastRealOrderId = $this->_objectManager->create('\Magento\Checkout\Model\Session')->getLastRealOrder()->getId(); - if (is_null($lastRealOrderId)) { + if ($lastRealOrderId===null) { throw new \Exception("There is no order associated with this session."); } diff --git a/Controller/Direct/Installments.php b/Controller/Direct/Installments.php index 0f9a845..7198ee3 100644 --- a/Controller/Direct/Installments.php +++ b/Controller/Direct/Installments.php @@ -185,7 +185,7 @@ private function lastRealOrderId() { $lastRealOrderId = $this->_objectManager->create('\Magento\Checkout\Model\Session')->getLastRealOrder()->getId(); - if (is_null($lastRealOrderId)) { + if ($lastRealOrderId=== null) { throw new \Exception("There is no order associated with this session."); } diff --git a/Controller/Direct/Search.code-search b/Controller/Direct/Search.code-search new file mode 100644 index 0000000..284f259 --- /dev/null +++ b/Controller/Direct/Search.code-search @@ -0,0 +1,63 @@ +# Query: is_null( + +30 results - 15 files + +pagseguro-modulo-magento-v2\Controller\Direct\Boleto.php: + 84: if (!is_null($this->order)) { + +pagseguro-modulo-magento-v2\Controller\Direct\Debit.php: + 85: if (!is_null($this->order)) { + 237: if ($lastRealOrderId=== null) { + +pagseguro-modulo-magento-v2\Controller\Direct\Installments.php: + 87: if (!is_null($this->order)) { + 188: if (is_null($lastRealOrderId)) { + +pagseguro-modulo-magento-v2\Controller\Payment\Request.php: + 93: if (is_null($lastRealOrder->getPayment())) { + 102: if (is_null($this->orderId)) { + 128: if (!is_null($this->order)) { + 142: if (is_null($this->orderId)) { + 172: if (!is_null($this->order)) { + 185: if (is_null($this->orderId)) { + 228: if (!is_null($this->order)) { + +pagseguro-modulo-magento-v2\Helper\Data.php: + 333: if (!is_null($cancellationSource)) { + 417: if (!is_null($type)) { + 450: if (!is_null($code)) { + +pagseguro-modulo-magento-v2\Model\PaymentMethod.php: + 209: if (!is_null($address) or !empty($adress)) { + +pagseguro-modulo-magento-v2\Model\Direct\BoletoMethod.php: + 343: if (!is_null($address) or !empty($adress)) + +pagseguro-modulo-magento-v2\Model\Direct\CreditCardMethod.php: + 428: if (!is_null($address) or !empty($adress)) + +pagseguro-modulo-magento-v2\Model\Direct\DebitMethod.php: + 349: if (!is_null($address) or !empty($adress)) + +pagseguro-modulo-magento-v2\Model\Transactions\Method.php: + 53: if (is_null($page)) $page = 1; + 58: if (is_null($this->_PagSeguroPaymentList)) { + 97: if (!is_null($this->_session->getData('store_id'))) { + +pagseguro-modulo-magento-v2\Model\Transactions\Methods\Abandoned.php: + 194: if (!is_null($this->_session->getData('store_id'))) { + 274: if (is_null($page)) $page = 1; + 278: if (is_null($this->_PagSeguroPaymentList)) { + +pagseguro-modulo-magento-v2\Model\Transactions\Methods\Cancellation.php: + 231: if (! is_null($this->_PagSeguroPaymentList->getTransactions())) { + +pagseguro-modulo-magento-v2\Model\Transactions\Methods\Conciliation.php: + 136: if (! is_null($this->_PagSeguroPaymentList->getTransactions())) { + +pagseguro-modulo-magento-v2\Model\Transactions\Methods\Refund.php: + 246: if (! is_null($this->_PagSeguroPaymentList->getTransactions())) { + +pagseguro-modulo-magento-v2\view\frontend\templates\success.phtml: + 28: getPaymentLink())): ?> + 38: getPaymentLink())): ?> diff --git a/Controller/Notification/Response.php b/Controller/Notification/Response.php index a188bb3..47b4129 100755 --- a/Controller/Notification/Response.php +++ b/Controller/Notification/Response.php @@ -23,11 +23,14 @@ namespace UOL\PagSeguro\Controller\Notification; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; + /** * Class Checkout * @package UOL\PagSeguro\Controller\Payment */ -class Response extends \Magento\Framework\App\Action\Action +class Response extends \Magento\Framework\App\Action\Action implements \Magento\Framework\App\CsrfAwareActionInterface { /** @@ -57,7 +60,33 @@ public function execute() $nofitication->init(); } catch (\Exception $ex) { //log already written in your pagseguro log file if pagseguro log is enabled in admin - exit; + return; } } + + /** + * Create exception in case CSRF validation failed. + * Return null if default exception will suffice. + * + * @param RequestInterface $request + * + * @return InvalidRequestException|null + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * Perform custom request validation. + * Return null if default validation is needed. + * + * @param RequestInterface $request + *createCsrfValidationException + * @return bool|null + */ + public function validateForCsrf(RequestInterface $request): ?bool { + return true; + } } diff --git a/Controller/Payment/Checkout.php b/Controller/Payment/Checkout.php index 62a7422..ee4c261 100755 --- a/Controller/Payment/Checkout.php +++ b/Controller/Payment/Checkout.php @@ -87,7 +87,7 @@ public function execute() */ private function getPagSeguroPaymentJs() { - if (\PagSeguro\Configuration\Configure::getEnvironment()->getEnvironment() == 'sandbox') { + if (\PagSeguro\Configuration\Configure::getEnvironment()->getEnvironment()=== 'sandbox') { return \UOL\PagSeguro\Helper\Library::SANDBOX_JS; } else { return \UOL\PagSeguro\Helper\Library::STANDARD_JS; diff --git a/Controller/Payment/Request.php b/Controller/Payment/Request.php index b43fdf0..8f18a88 100755 --- a/Controller/Payment/Request.php +++ b/Controller/Payment/Request.php @@ -90,7 +90,7 @@ public function execute() { $lastRealOrder = $this->_checkoutSession->getLastRealOrder(); - if (is_null($lastRealOrder->getPayment())) { + if ($lastRealOrder->getPayment()===null) { throw new \Magento\Framework\Exception\NotFoundException(__('No order associated.')); } @@ -99,7 +99,7 @@ public function execute() if ($paymentData['method'] === 'pagseguro_boleto') { try { $this->orderId = $lastRealOrder->getId(); - if (is_null($this->orderId)) { + if ($this->orderId===null) { throw new \Exception("There is no order associated with this session."); } @@ -139,7 +139,7 @@ public function execute() try { $this->orderId = $lastRealOrder->getId(); - if (is_null($this->orderId)) { + if ($this->orderId===null) { throw new \Exception("There is no order associated with this session."); } @@ -182,7 +182,7 @@ public function execute() try { $this->orderId = $lastRealOrder->getId(); - if (is_null($this->orderId)) { + if ($this->orderId===null) { throw new \Exception("There is no order associated with this session."); } diff --git a/Helper/Data.php b/Helper/Data.php index 089627d..e12ac22 100755 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -44,7 +44,8 @@ class Data 6 => "pagseguro_devolvida", 7 => "pagseguro_cancelada", 8 => "pagseguro_chargeback_debitado", - 9 => "pagseguro_em_contestacao" + 9 => "pagseguro_em_contestacao", + 10 => "partially_refunded" ); /** @@ -86,11 +87,11 @@ public static function addressConfig($fullAddress) $complement = ''; $district = ''; $broken = preg_split('/[-,\\n]/', $fullAddress); - if (sizeof($broken) == 4) { + if (sizeof($broken)=== 4) { list($address, $number, $complement, $district) = $broken; - } elseif (sizeof($broken) == 3) { + } elseif (sizeof($broken)=== 3) { list($address, $number, $complement) = $broken; - } elseif (sizeof($broken) == 2 || sizeof($broken) == 1) { + } elseif (sizeof($broken)=== 2 || sizeof($broken)=== 1) { list($address, $number, $complement) = self::sortData($fullAddress); } else { $address = $fullAddress; @@ -269,7 +270,7 @@ public static function formatPhone($phone) $phone = self::keepOnlyNumbers($phone); // removes leading zero - if (substr($phone, 0, 1) == 0) { + if (substr($phone, 0, 1)=== 0) { $phone = substr($phone, 1); } @@ -322,4 +323,213 @@ public static function keepOnlyNumbers($data) { return preg_replace('/[^0-9]/', '', $data); } + + /** + * @param $type + * @return bool|string + */ + public static function getTitleCancellationSourceTransaction($cancellationSource) + { + if (!is_null($cancellationSource)) { + switch ($cancellationSource) { + case "INTERNAL": + return 'PagSeguro'; + break; + case "EXTERNAL": + return 'Instituições Financeiras'; + break; + default: + return $cancellationSource; + break; + } + } + + return false; + } + + /** + * Translates the transaction type name to his respective name, according with the api + * + * @param int $transactionTypeCode + * @return mixed string | int + */ + public static function getTransactionTypeName($transactionTypeCode) + { + if ($transactionTypeCode) { + switch ($transactionTypeCode) { + case 1: + return 'Venda'; + break; + case 2: + return 'Transferência'; + break; + case 3: + return 'Adição de fundos'; + break; + case 4: + return 'Saque'; + break; + case 5: + return 'Cobrança'; + break; + case 6: + return 'Doação'; + break; + case 7: + return 'Bônus'; + break; + case 8: + return 'Repasse de bônus'; + break; + case 9: + return 'Operacional'; + break; + case 10: + return 'Doação pública'; + break; + case 11: + return 'Pagamento pré aprovado'; + break; + case 12: + return 'Campanha bônus'; + break; + case 13: + return 'Secundária'; + break; + case 14: + return 'Validador'; + break; + default: + return $transactionTypeCode; + break; + } + } + } + + /** + * Translates the transaction payment method type title to his respective name, according with the api + * @param $type + * + * @return bool|string + */ + public static function getTitleTypePaymentMethod($type) + { + if (!is_null($type)) { + switch ($type) { + case 1: + return 'Cartão de crédito'; + break; + case 2: + return 'Boleto'; + break; + case 3: + return 'Débito online(TEF)'; + break; + case 4: + return 'Saldo PagSeguro'; + break; + case 7: + return 'Depósito em conta'; + break; + default: + return $type; + break; + } + } + return false; + } + + /** + * Translates the transaction payment method code title to his respective name, according with the api + * @param $code + * + * @return bool|string + */ + public static function getTitleCodePaymentMethod($code) + { + if (!is_null($code)) { + switch ($code) { + case 101: + return 'Cartão de crédito Visa'; + break; + case 102: + return 'Cartão de crédito MasterCard'; + break; + case 103: + return 'Cartão de crédito American Express'; + break; + case 104: + return 'Cartão de crédito Diners'; + break; + case 105: + return 'Cartão de crédito Hipercard'; + break; + case 106: + return 'Cartão de crédito Aura'; + break; + case 107: + return 'Cartão de crédito Elo'; + break; + case 109: + return 'Cartão de crédito PersonalCard'; + break; + case 112: + return 'Cartão de crédito BrasilCard'; + break; + case 113: + return 'Cartão de crédito FORTBRASIL'; + break; + case 115: + return 'Cartão de crédito VALECARD'; + break; + case 116: + return 'Cartão de crédito Cabal'; + break; + case 117: + return 'Cartão de crédito Mais!'; + break; + case 119: + return 'Cartão de crédito GRANDCARD'; + break; + case 120: + return 'Cartão de crédito Sorocred'; + break; + case 122: + return 'Cartão de crédito Up Policard'; + break; + case 123: + return 'Cartão de crédito Banese Card'; + break; + case 201: + return 'Boleto Bradesco'; + break; + case 202: + return 'Boleto Santander'; + break; + case 301: + return 'Débito online Bradesco'; + break; + case 302: + return 'Débito online Itaú'; + break; + case 304: + return 'Débito online Banco do Brasil'; + break; + case 306: + return 'Débito online Banrisul'; + break; + case 401: + return 'Saldo PagSeguro'; + break; + case 701: + return 'Depósito em conta - Banco do Brasil'; + break; + default: + return $code; + break; + } + } + return false; + } + } diff --git a/Helper/Library.php b/Helper/Library.php index 238559c..d15faf6 100644 --- a/Helper/Library.php +++ b/Helper/Library.php @@ -84,7 +84,7 @@ public function getPagSeguroCredentials() public function isLightboxCheckoutType() { if ($this->_scopeConfig->getValue('payment/pagseguro_default_lightbox/checkout') - == \UOL\PagSeguro\Model\System\Config\Checkout::LIGHTBOX) { + === \UOL\PagSeguro\Model\System\Config\Checkout::LIGHTBOX) { return true; } return false; @@ -172,7 +172,7 @@ public function getSession() */ public function getDirectPaymentUrl() { - if ($this->getEnvironment() == 'sandbox') { + if ($this->getEnvironment()=== 'sandbox') { return Library::DIRECT_PAYMENT_URL_SANDBOX; } else { return Library::DIRECT_PAYMENT_URL; diff --git a/Model/Direct/BoletoMethod.php b/Model/Direct/BoletoMethod.php index 267c31a..c2b91aa 100644 --- a/Model/Direct/BoletoMethod.php +++ b/Model/Direct/BoletoMethod.php @@ -105,6 +105,7 @@ public function createPaymentRequest() $this->urls(); $this->items(); $this->config(); + $this->setShoppingCartRecovery(); return $this->register(); } catch (\Exception $exception) { throw $exception; @@ -163,17 +164,12 @@ private function reference() */ private function shipping() { - $this->setShippingInformation(); - //Shipping Type - $this->_paymentRequest->setShipping()->setType()->withParameters(\PagSeguro\Enum\Shipping\Type::NOT_SPECIFIED); - //Shipping Coast - $this->_paymentRequest->setShipping()->setCost()->withParameters(number_format( - $this->getShippingAmount(), - 2, - '.', - null //'' - ) - ); + if ($this->_order->getIsVirtual()) { + $this->_paymentRequest->setShipping()->setAddressRequired()->withParameters('false'); + } else { + $this->_paymentRequest->setShipping()->setAddressRequired()->withParameters('true'); + $this->setShippingInformation(); + } } /** @@ -222,7 +218,7 @@ private function setSenderDocument() private function setItemsInformation($product) { $this->_paymentRequest->addItems()->withParameters( - $product->getId(), //id + $product->getProduct()->getId(), //id \UOL\PagSeguro\Helper\Data::fixStringLength($product->getName(), 255), //description $product->getSimpleQtyToShip(), //quantity \UOL\PagSeguro\Helper\Data::toFloat($product->getPrice()), //amount @@ -236,9 +232,9 @@ private function setItemsInformation($product) private function setSenderInformation() { if ( - $this->_order->getCustomerName() == (string)__('Guest') - || $this->_order->getCustomerName() == 'Convidado' - || $this->_order->getCustomerName() == 'Visitante' + $this->_order->getCustomerName()=== (string)__('Guest') + || $this->_order->getCustomerName()=== 'Convidado' + || $this->_order->getCustomerName()=== 'Visitante' ) { $this->guest(); } else { @@ -272,7 +268,7 @@ private function loggedIn() */ private function getEmail() { -// if ($this->_scopeConfig->getValue('payment/pagseguro/environment') == "sandbox") { +// if ($this->_scopeConfig->getValue('payment/pagseguro/environment')=== "sandbox") { // return "magento2@sandbox.pagseguro.com.br"; //mock for sandbox // } return $this->_order->getCustomerEmail(); @@ -283,9 +279,12 @@ private function getEmail() */ private function setSenderPhone() { - $shipping = $this->getShippingData(); - if (! empty($shipping['telephone'])) { - $phone = \UOL\PagSeguro\Helper\Data::formatPhone($shipping['telephone']); + $addressData = ($this->getBillingAddress()) + ? $this->getBillingAddress() + : $this->_order->getShippingAddress(); + + if (! empty($addressData['telephone'])) { + $phone = \UOL\PagSeguro\Helper\Data::formatPhone($addressData['telephone']); $this->_paymentRequest->setSender()->setPhone()->withParameters( $phone['areaCode'], $phone['number'] @@ -298,19 +297,38 @@ private function setSenderPhone() */ private function setShippingInformation() { - $shipping = $this->getShippingData(); - $address = \UOL\PagSeguro\Helper\Data::addressConfig($shipping['street']); - - $this->_paymentRequest->setShipping()->setAddress()->withParameters( - $this->getShippingAddress($address[0], $shipping), - $this->getShippingAddress($address[1]), - $this->getShippingAddress($address[0]), - \UOL\PagSeguro\Helper\Data::fixPostalCode($shipping->getPostcode()), - $shipping->getCity(), - $this->getRegionAbbreviation($shipping), - $this->getCountryName($shipping['country_id']), - $this->getShippingAddress($address[2]) - ); + $shipping = $this->_order->getShippingAddress(); + if ($shipping) { + if (count($shipping->getStreet()) === 4) { + $this->_paymentRequest->setShipping()->setAddress()->withParameters( + $shipping->getStreetLine(1), + $shipping->getStreetLine(2), + $shipping->getStreetLine(4), + \UOL\PagSeguro\Helper\Data::fixPostalCode($shipping->getPostcode()), + $shipping->getCity(), + $this->getRegionAbbreviation($shipping), + $this->getCountryName($shipping['country_id']), + $shipping->getStreetLine(3) + ); + } else { + $address = \UOL\PagSeguro\Helper\Data::addressConfig($shipping['street']); + $this->_paymentRequest->setShipping()->setAddress()->withParameters( + $this->getShippingAddress($address[0], $shipping), + $this->getShippingAddress($address[1]), + $this->getShippingAddress($address[3]), + \UOL\PagSeguro\Helper\Data::fixPostalCode($shipping->getPostcode()), + $shipping->getCity(), + $this->getRegionAbbreviation($shipping), + $this->getCountryName($shipping['country_id']), + $this->getShippingAddress($address[2]) + ); + } + + $this->_paymentRequest->setShipping()->setType() + ->withParameters(\PagSeguro\Enum\Shipping\Type::NOT_SPECIFIED); //Shipping Type + $this->_paymentRequest->setShipping()->setCost() + ->withParameters(number_format($this->getShippingAmount(), 2, '.', '')); //Shipping Coast + } } /** @@ -329,18 +347,6 @@ private function getShippingAddress($address, $shipping = null) return null; } - /** - * Get the shipping Data of the Order - * - * @return object $orderParams - Return parameters, of shipping of order - */ - private function getShippingData() - { - if ($this->_order->getIsVirtual()) - return $this->getBillingAddress(); - return $this->_order->getShippingAddress(); - } - /** * Get shipping amount from magento order * @@ -372,7 +378,7 @@ private function getOrderStoreReference() */ private function getRegionAbbreviation($shipping) { - if (strlen($shipping->getRegionCode()) == 2) { + if (strlen($shipping->getRegionCode())=== 2) { return $shipping->getRegionCode(); } @@ -433,4 +439,18 @@ private function discounts() { $this->_paymentRequest->setExtraAmount(round($this->_order->getDiscountAmount(), 2)); } + + /** + * Set PagSeguro recovery shopping cart value + * + * @return void + */ + private function setShoppingCartRecovery() + { + if ($this->_scopeConfig->getValue('payment/pagseguro/shopping_cart_recovery')=== true) { + $this->_paymentRequest->addParameter()->withParameters('enableRecovery', 'true'); + } else { + $this->_paymentRequest->addParameter()->withParameters('enableRecovery', 'false'); + } + } } diff --git a/Model/Direct/CreditCardMethod.php b/Model/Direct/CreditCardMethod.php index f12294a..e9fff20 100644 --- a/Model/Direct/CreditCardMethod.php +++ b/Model/Direct/CreditCardMethod.php @@ -97,6 +97,7 @@ public function createPaymentRequest() $this->token(); $this->holder(); $this->config(); + $this->setShoppingCartRecovery(); return $this->register(); } catch (\Exception $exception) { throw $exception; @@ -155,17 +156,12 @@ private function reference() */ private function shipping() { - $this->setShippingInformation(); - //Shipping Type - $this->_paymentRequest->setShipping()->setType()->withParameters(\PagSeguro\Enum\Shipping\Type::NOT_SPECIFIED); - //Shipping Coast - $this->_paymentRequest->setShipping()->setCost()->withParameters(number_format( - $this->getShippingAmount(), - 2, - '.', - null //'' - ) - ); + if ($this->_order->getIsVirtual()) { + $this->_paymentRequest->setShipping()->setAddressRequired()->withParameters('false'); + } else { + $this->_paymentRequest->setShipping()->setAddressRequired()->withParameters('true'); + $this->setShippingInformation(); + } } private function billing() @@ -235,9 +231,12 @@ private function holder() */ private function setHolderPhone() { - $shipping = $this->getShippingData(); - if (! empty($shipping['telephone'])) { - $phone = \UOL\PagSeguro\Helper\Data::formatPhone($shipping['telephone']); + $addressData = ($this->getBillingAddress()) + ? $this->getBillingAddress() + : $this->_order->getShippingAddress(); + + if (! empty($addressData['telephone'])) { + $phone = \UOL\PagSeguro\Helper\Data::formatPhone($addressData['telephone']); $this->_paymentRequest->setHolder()->setPhone()->withParameters( $phone['areaCode'], $phone['number'] @@ -250,29 +249,35 @@ private function setHolderPhone() */ private function setBillingInformation() { - $billing = $this->getBillingData(); - $address = \UOL\PagSeguro\Helper\Data::addressConfig($billing['street']); - $this->_paymentRequest->setBilling()->setAddress()->withParameters( - $this->getShippingAddress($address[0], $billing), - $this->getShippingAddress($address[1]), - $this->getShippingAddress($address[0]), - \UOL\PagSeguro\Helper\Data::fixPostalCode($billing->getPostcode()), - $billing->getCity(), - $this->getRegionAbbreviation($billing), - $this->getCountryName($billing['country_id']), - $this->getShippingAddress($address[2]) - ); - } - - /** - * Get the billing Data of the Order - * @return object $orderParams - Return parameters, of billing of order - */ - private function getBillingData() - { - $billingAddress = $this->getBillingAddress(); - return (!empty($billingAddress)) ? $billingAddress : $this->_order->getShippingAddress(); + $billing = $this->getBillingAddress(); + if ($billing) { + if (count($billing->getStreet()) === 4) { + $this->_paymentRequest->setBilling()->setAddress()->withParameters( + $billing->getStreetLine(1), + $billing->getStreetLine(2), + $billing->getStreetLine(4), + \UOL\PagSeguro\Helper\Data::fixPostalCode($billing->getPostcode()), + $billing->getCity(), + $this->getRegionAbbreviation($billing), + $this->getCountryName($billing['country_id']), + $billing->getStreetLine(3) + ); + } else { + $address = \UOL\PagSeguro\Helper\Data::addressConfig($billing['street']); + $this->_paymentRequest->setBilling()->setAddress()->withParameters( + $this->getShippingAddress($address[0], $billing), + $this->getShippingAddress($address[1]), + $this->getShippingAddress($address[3]), + \UOL\PagSeguro\Helper\Data::fixPostalCode($billing->getPostcode()), + $billing->getCity(), + $this->getRegionAbbreviation($billing), + $this->getCountryName($billing['country_id']), + $this->getShippingAddress($address[2]) + ); + } + } } + /** * Set sender hash */ @@ -298,7 +303,7 @@ private function setSenderDocument() private function setItemsInformation($product) { $this->_paymentRequest->addItems()->withParameters( - $product->getId(), //id + $product->getProduct()->getId(), //id \UOL\PagSeguro\Helper\Data::fixStringLength($product->getName(), 255), //description $product->getSimpleQtyToShip(), //quantity \UOL\PagSeguro\Helper\Data::toFloat($product->getPrice()), //amount @@ -312,9 +317,9 @@ private function setItemsInformation($product) private function setSenderInformation() { if ( - $this->_order->getCustomerName() == (string)__('Guest') - || $this->_order->getCustomerName() == 'Convidado' - || $this->_order->getCustomerName() == 'Visitante' + $this->_order->getCustomerName()=== (string)__('Guest') + || $this->_order->getCustomerName()=== 'Convidado' + || $this->_order->getCustomerName()=== 'Visitante' ) { $this->guest(); } else { @@ -348,7 +353,7 @@ private function loggedIn() */ private function getEmail() { -// if ($this->_scopeConfig->getValue('payment/pagseguro/environment') == "sandbox") { +// if ($this->_scopeConfig->getValue('payment/pagseguro/environment')=== "sandbox") { // return "magento2@sandbox.pagseguro.com.br"; //mock for sandbox // } return $this->_order->getCustomerEmail(); @@ -359,9 +364,12 @@ private function getEmail() */ private function setSenderPhone() { - $shipping = $this->getShippingData(); - if (! empty($shipping['telephone'])) { - $phone = \UOL\PagSeguro\Helper\Data::formatPhone($shipping['telephone']); + $addressData = ($this->getBillingAddress()) + ? $this->getBillingAddress() + : $this->_order->getShippingAddress(); + + if (! empty($addressData['telephone'])) { + $phone = \UOL\PagSeguro\Helper\Data::formatPhone($addressData['telephone']); $this->_paymentRequest->setSender()->setPhone()->withParameters( $phone['areaCode'], $phone['number'] @@ -374,19 +382,38 @@ private function setSenderPhone() */ private function setShippingInformation() { - $shipping = $this->getShippingData(); - $address = \UOL\PagSeguro\Helper\Data::addressConfig($shipping['street']); - - $this->_paymentRequest->setShipping()->setAddress()->withParameters( - $this->getShippingAddress($address[0], $shipping), - $this->getShippingAddress($address[1]), - $this->getShippingAddress($address[0]), - \UOL\PagSeguro\Helper\Data::fixPostalCode($shipping->getPostcode()), - $shipping->getCity(), - $this->getRegionAbbreviation($shipping), - $this->getCountryName($shipping['country_id']), - $this->getShippingAddress($address[2]) - ); + $shipping = $this->_order->getShippingAddress(); + if ($shipping) { + if (count($shipping->getStreet()) === 4) { + $this->_paymentRequest->setShipping()->setAddress()->withParameters( + $shipping->getStreetLine(1), + $shipping->getStreetLine(2), + $shipping->getStreetLine(4), + \UOL\PagSeguro\Helper\Data::fixPostalCode($shipping->getPostcode()), + $shipping->getCity(), + $this->getRegionAbbreviation($shipping), + $this->getCountryName($shipping['country_id']), + $shipping->getStreetLine(3) + ); + } else { + $address = \UOL\PagSeguro\Helper\Data::addressConfig($shipping['street']); + $this->_paymentRequest->setShipping()->setAddress()->withParameters( + $this->getShippingAddress($address[0], $shipping), + $this->getShippingAddress($address[1]), + $this->getShippingAddress($address[3]), + \UOL\PagSeguro\Helper\Data::fixPostalCode($shipping->getPostcode()), + $shipping->getCity(), + $this->getRegionAbbreviation($shipping), + $this->getCountryName($shipping['country_id']), + $this->getShippingAddress($address[2]) + ); + } + + $this->_paymentRequest->setShipping()->setType() + ->withParameters(\PagSeguro\Enum\Shipping\Type::NOT_SPECIFIED); //Shipping Type + $this->_paymentRequest->setShipping()->setCost() + ->withParameters(number_format($this->getShippingAmount(), 2, '.', '')); //Shipping Coast + } } /** @@ -405,18 +432,6 @@ private function getShippingAddress($address, $shipping = null) return null; } - /** - * Get the shipping Data of the Order - * - * @return object $orderParams - Return parameters, of shipping of order - */ - private function getShippingData() - { - if ($this->_order->getIsVirtual()) - return $this->getBillingAddress(); - return $this->_order->getShippingAddress(); - } - /** * Get shipping amount from magento order * @@ -448,7 +463,7 @@ private function getOrderStoreReference() */ private function getRegionAbbreviation($shipping) { - if (strlen($shipping->getRegionCode()) == 2) { + if (strlen($shipping->getRegionCode())=== 2) { return $shipping->getRegionCode(); } @@ -509,4 +524,18 @@ private function discounts() { $this->_paymentRequest->setExtraAmount(round($this->_order->getDiscountAmount(), 2)); } + + /** + * Set PagSeguro recovery shopping cart value + * + * @return void + */ + private function setShoppingCartRecovery() + { + if ($this->_scopeConfig->getValue('payment/pagseguro/shopping_cart_recovery')=== true) { + $this->_paymentRequest->addParameter()->withParameters('enableRecovery', 'true'); + } else { + $this->_paymentRequest->addParameter()->withParameters('enableRecovery', 'false'); + } + } } diff --git a/Model/Direct/DebitMethod.php b/Model/Direct/DebitMethod.php index 6b8f8c7..ccf1ad9 100644 --- a/Model/Direct/DebitMethod.php +++ b/Model/Direct/DebitMethod.php @@ -103,6 +103,7 @@ public function createPaymentRequest() $this->items(); $this->config(); $this->bank(); + $this->setShoppingCartRecovery(); return $this->register(); } catch (\Exception $exception) { throw $exception; @@ -161,17 +162,12 @@ private function reference() */ private function shipping() { - $this->setShippingInformation(); - //Shipping Type - $this->_paymentRequest->setShipping()->setType()->withParameters(\PagSeguro\Enum\Shipping\Type::NOT_SPECIFIED); - //Shipping Coast - $this->_paymentRequest->setShipping()->setCost()->withParameters(number_format( - $this->getShippingAmount(), - 2, - '.', - null //'' - ) - ); + if ($this->_order->getIsVirtual()) { + $this->_paymentRequest->setShipping()->setAddressRequired()->withParameters('false'); + } else { + $this->_paymentRequest->setShipping()->setAddressRequired()->withParameters('true'); + $this->setShippingInformation(); + } } /** @@ -228,7 +224,7 @@ private function setSenderDocument() private function setItemsInformation($product) { $this->_paymentRequest->addItems()->withParameters( - $product->getId(), //id + $product->getProduct()->getId(), //id \UOL\PagSeguro\Helper\Data::fixStringLength($product->getName(), 255), //description $product->getSimpleQtyToShip(), //quantity \UOL\PagSeguro\Helper\Data::toFloat($product->getPrice()), //amount @@ -242,9 +238,9 @@ private function setItemsInformation($product) private function setSenderInformation() { if ( - $this->_order->getCustomerName() == (string)__('Guest') - || $this->_order->getCustomerName() == 'Convidado' - || $this->_order->getCustomerName() == 'Visitante' + $this->_order->getCustomerName()=== (string)__('Guest') + || $this->_order->getCustomerName()=== 'Convidado' + || $this->_order->getCustomerName()=== 'Visitante' ) { $this->guest(); } else { @@ -278,7 +274,7 @@ private function loggedIn() */ private function getEmail() { -// if ($this->_scopeConfig->getValue('payment/pagseguro/environment') == "sandbox") { +// if ($this->_scopeConfig->getValue('payment/pagseguro/environment')=== "sandbox") { // return "magento2@sandbox.pagseguro.com.br"; //mock for sandbox // } return $this->_order->getCustomerEmail(); @@ -289,9 +285,12 @@ private function getEmail() */ private function setSenderPhone() { - $shipping = $this->getShippingData(); - if (! empty($shipping['telephone'])) { - $phone = \UOL\PagSeguro\Helper\Data::formatPhone($shipping['telephone']); + $addressData = ($this->getBillingAddress()) + ? $this->getBillingAddress() + : $this->_order->getShippingAddress(); + + if (! empty($addressData['telephone'])) { + $phone = \UOL\PagSeguro\Helper\Data::formatPhone($addressData['telephone']); $this->_paymentRequest->setSender()->setPhone()->withParameters( $phone['areaCode'], $phone['number'] @@ -304,19 +303,38 @@ private function setSenderPhone() */ private function setShippingInformation() { - $shipping = $this->getShippingData(); - $address = \UOL\PagSeguro\Helper\Data::addressConfig($shipping['street']); - - $this->_paymentRequest->setShipping()->setAddress()->withParameters( - $this->getShippingAddress($address[0], $shipping), - $this->getShippingAddress($address[1]), - $this->getShippingAddress($address[0]), - \UOL\PagSeguro\Helper\Data::fixPostalCode($shipping->getPostcode()), - $shipping->getCity(), - $this->getRegionAbbreviation($shipping), - $this->getCountryName($shipping['country_id']), - $this->getShippingAddress($address[2]) - ); + $shipping = $this->_order->getShippingAddress(); + if ($shipping) { + if (count($shipping->getStreet()) === 4) { + $this->_paymentRequest->setShipping()->setAddress()->withParameters( + $shipping->getStreetLine(1), + $shipping->getStreetLine(2), + $shipping->getStreetLine(4), + \UOL\PagSeguro\Helper\Data::fixPostalCode($shipping->getPostcode()), + $shipping->getCity(), + $this->getRegionAbbreviation($shipping), + $this->getCountryName($shipping['country_id']), + $shipping->getStreetLine(3) + ); + } else { + $address = \UOL\PagSeguro\Helper\Data::addressConfig($shipping['street']); + $this->_paymentRequest->setShipping()->setAddress()->withParameters( + $this->getShippingAddress($address[0], $shipping), + $this->getShippingAddress($address[1]), + $this->getShippingAddress($address[3]), + \UOL\PagSeguro\Helper\Data::fixPostalCode($shipping->getPostcode()), + $shipping->getCity(), + $this->getRegionAbbreviation($shipping), + $this->getCountryName($shipping['country_id']), + $this->getShippingAddress($address[2]) + ); + } + + $this->_paymentRequest->setShipping()->setType() + ->withParameters(\PagSeguro\Enum\Shipping\Type::NOT_SPECIFIED); //Shipping Type + $this->_paymentRequest->setShipping()->setCost() + ->withParameters(number_format($this->getShippingAmount(), 2, '.', '')); //Shipping Coast + } } /** @@ -335,18 +353,6 @@ private function getShippingAddress($address, $shipping = null) return null; } - /** - * Get the shipping Data of the Order - * - * @return object $orderParams - Return parameters, of shipping of order - */ - private function getShippingData() - { - if ($this->_order->getIsVirtual()) - return $this->getBillingAddress(); - return $this->_order->getShippingAddress(); - } - /** * Get shipping amount from magento order * @@ -378,7 +384,7 @@ private function getOrderStoreReference() */ private function getRegionAbbreviation($shipping) { - if (strlen($shipping->getRegionCode()) == 2) { + if (strlen($shipping->getRegionCode())=== 2) { return $shipping->getRegionCode(); } @@ -439,4 +445,18 @@ private function discounts() { $this->_paymentRequest->setExtraAmount(round($this->_order->getDiscountAmount(), 2)); } + + /** + * Set PagSeguro recovery shopping cart value + * + * @return void + */ + private function setShoppingCartRecovery() + { + if ($this->_scopeConfig->getValue('payment/pagseguro/shopping_cart_recovery')=== true) { + $this->_paymentRequest->addParameter()->withParameters('enableRecovery', 'true'); + } else { + $this->_paymentRequest->addParameter()->withParameters('enableRecovery', 'false'); + } + } } diff --git a/Model/Direct/InstallmentsMethod.php b/Model/Direct/InstallmentsMethod.php index 9de98d4..17573b8 100644 --- a/Model/Direct/InstallmentsMethod.php +++ b/Model/Direct/InstallmentsMethod.php @@ -215,7 +215,7 @@ private function getInstallmentText($installment) */ private function getInterestFreeText($insterestFree) { - return ($insterestFree == 'true') ? 'sem' : 'com'; + return ($insterestFree=== 'true') ? 'sem' : 'com'; } /** diff --git a/Model/NotificationMethod.php b/Model/NotificationMethod.php index bcba162..5028bf4 100644 --- a/Model/NotificationMethod.php +++ b/Model/NotificationMethod.php @@ -157,7 +157,7 @@ private function getTransaction() */ private function compareStatus($pagseguro, $magento) { - if ($pagseguro == $magento) { + if ($pagseguro=== $magento) { return true; } return false; diff --git a/Model/Payment.php b/Model/Payment.php index 1159dd1..0b05486 100644 --- a/Model/Payment.php +++ b/Model/Payment.php @@ -100,7 +100,7 @@ public function __construct( */ public function isDirectCheckout() { -// if ($this->getConfigData('checkout') == \UOL\PagSeguro\Model\System\Config\Checkout::DIRECT) { +// if ($this->getConfigData('checkout')=== \UOL\PagSeguro\Model\System\Config\Checkout::DIRECT) { // return true; // } return false; @@ -113,7 +113,7 @@ public function isDirectCheckout() */ public function isLightboxCheckoutType() { - if ($this->getConfigData('checkout') == \UOL\PagSeguro\Model\System\Config\Checkout::LIGHTBOX) { + if ($this->getConfigData('checkout')=== \UOL\PagSeguro\Model\System\Config\Checkout::LIGHTBOX) { return true; } return false; diff --git a/Model/PaymentDefaultlLightbox.php b/Model/PaymentDefaultlLightbox.php index 21307f0..178f4b2 100644 --- a/Model/PaymentDefaultlLightbox.php +++ b/Model/PaymentDefaultlLightbox.php @@ -100,7 +100,7 @@ public function __construct( */ public function isDirectCheckout() { -// if ($this->getConfigData('checkout') == \UOL\PagSeguro\Model\System\Config\Checkout::DIRECT) { +// if ($this->getConfigData('checkout')=== \UOL\PagSeguro\Model\System\Config\Checkout::DIRECT) { // return true; // } return false; @@ -113,7 +113,7 @@ public function isDirectCheckout() */ public function isLightboxCheckoutType() { - if ($this->getConfigData('checkout') == \UOL\PagSeguro\Model\System\Config\Checkout::LIGHTBOX) { + if ($this->getConfigData('checkout')=== \UOL\PagSeguro\Model\System\Config\Checkout::LIGHTBOX) { return true; } return false; diff --git a/Model/PaymentMethod.php b/Model/PaymentMethod.php index d3c59a7..b055538 100644 --- a/Model/PaymentMethod.php +++ b/Model/PaymentMethod.php @@ -87,12 +87,10 @@ public function createPaymentRequest() // Cart discount $lastRealOrder = $this->_checkoutSession->getLastRealOrder(); $this->_paymentRequest->setExtraAmount(round($lastRealOrder->getDiscountAmount(), 2)); + // PagSeguro Payment Method discounts + $this->setPagSeguroDiscountsByPaymentMethod(); //Shipping $this->setShippingInformation(); - $this->_paymentRequest->setShipping()->setType() - ->withParameters(\PagSeguro\Enum\Shipping\Type::NOT_SPECIFIED); //Shipping Type - $this->_paymentRequest->setShipping()->setCost() - ->withParameters(number_format($this->getShippingAmount(), 2, '.', '')); //Shipping Coast // Sender $this->setSenderInformation(); // Itens @@ -101,6 +99,8 @@ public function createPaymentRequest() $this->_paymentRequest->setRedirectUrl($this->getRedirectUrl()); // Notification Url $this->_paymentRequest->setNotificationUrl($this->getNotificationUrl()); + // Shopping cart recovery + $this->setShoppingCartRecovery(); try { $this->_library->setEnvironment(); $this->_library->setCharset(); @@ -123,7 +123,7 @@ private function setItemsInformation() { foreach ($this->_checkoutSession->getLastRealOrder()->getAllVisibleItems() as $product) { $this->_paymentRequest->addItems()->withParameters( - $product->getId(), //id + $product->getProduct()->getId(), //id \UOL\PagSeguro\Helper\Data::fixStringLength($product->getName(), 255), //description $product->getSimpleQtyToShip(), //quantity \UOL\PagSeguro\Helper\Data::toFloat($product->getPrice()), //amount @@ -139,9 +139,9 @@ private function setSenderInformation() $senderName = $this->_checkoutSession->getLastRealOrder()->getCustomerName(); // If Guest if ( - $senderName == (string)__('Guest') - || $senderName == 'Convidado' - || $senderName == 'Visitante' + $senderName=== (string)__('Guest') + || $senderName=== 'Convidado' + || $senderName=== 'Visitante' ) { $address = $this->getBillingAddress(); @@ -158,19 +158,44 @@ private function setSenderInformation() */ private function setShippingInformation() { - $shipping = $this->getShippingData(); - $address = \UOL\PagSeguro\Helper\Data::addressConfig($shipping['street']); + if ($this->_checkoutSession->getLastRealOrder()->getIsVirtual()) { + $this->_paymentRequest->setShipping()->setAddressRequired()->withParameters('false'); + } else { + $this->_paymentRequest->setShipping()->setAddressRequired()->withParameters('true'); + $shipping = $this->_checkoutSession->getLastRealOrder()->getShippingAddress(); + if ($shipping) { + if (count($shipping->getStreet()) === 4) { + $this->_paymentRequest->setShipping()->setAddress()->withParameters( + $shipping->getStreetLine(1), + $shipping->getStreetLine(2), + $shipping->getStreetLine(4), + \UOL\PagSeguro\Helper\Data::fixPostalCode($shipping->getPostcode()), + $shipping->getCity(), + $this->getRegionAbbreviation($shipping), + $this->getCountryName($shipping['country_id']), + $shipping->getStreetLine(3) + ); + } else { + $address = \UOL\PagSeguro\Helper\Data::addressConfig($shipping['street']); - $this->_paymentRequest->setShipping()->setAddress()->withParameters( - $this->getShippingAddress($address[0], $shipping), - $this->getShippingAddress($address[1]), - $this->getShippingAddress($address[3]), - \UOL\PagSeguro\Helper\Data::fixPostalCode($shipping->getPostcode()), - $shipping->getCity(), - $this->getRegionAbbreviation($shipping), - $this->getCountryName($shipping['country_id']), - $this->getShippingAddress($address[2]) - ); + $this->_paymentRequest->setShipping()->setAddress()->withParameters( + $this->getShippingAddress($address[0], $shipping), + $this->getShippingAddress($address[1]), + $this->getShippingAddress($address[3]), + \UOL\PagSeguro\Helper\Data::fixPostalCode($shipping->getPostcode()), + $shipping->getCity(), + $this->getRegionAbbreviation($shipping), + $this->getCountryName($shipping['country_id']), + $this->getShippingAddress($address[2]) + ); + } + + $this->_paymentRequest->setShipping()->setType() + ->withParameters(\PagSeguro\Enum\Shipping\Type::NOT_SPECIFIED); //Shipping Type + $this->_paymentRequest->setShipping()->setCost() + ->withParameters(number_format($this->getShippingAmount(), 2, '.', '')); //Shipping Coast + } + } } /** * Get shipping address @@ -190,19 +215,6 @@ private function getShippingAddress($address, $shipping = null) return null; } - /** - * Get the shipping Data of the Order - * - * @return object $orderParams - Return parameters, of shipping of order - */ - private function getShippingData() - { - if ($this->_checkoutSession->getLastRealOrder()->getIsVirtual()) { - return $this->getBillingAddress(); - } - return $this->_checkoutSession->getLastRealOrder()->getShippingAddress(); - } - /** * Get shipping amount from session * @@ -246,7 +258,7 @@ private function getOrderStoreReference() */ private function getRegionAbbreviation($shipping) { - if (strlen($shipping->getRegionCode()) == 2) { + if (strlen($shipping->getRegionCode())=== 2) { return $shipping->getRegionCode(); } @@ -282,13 +294,16 @@ public function getRedirectUrl() */ private function setSenderPhone() { - $shipping = $this->getShippingData(); - if (! empty($shipping['telephone'])) { - $phone = \UOL\PagSeguro\Helper\Data::formatPhone($shipping['telephone']); + $addressData = ($this->getBillingAddress()) + ? $this->getBillingAddress() + : $this->_checkoutSession->getLastRealOrder()->getShippingAddress(); + + if (! empty($addressData['telephone'])) { + $phone = \UOL\PagSeguro\Helper\Data::formatPhone($addressData['telephone']); $this->_paymentRequest->setSender()->setPhone()->withParameters( $phone['areaCode'], $phone['number'] - ); + ); } } @@ -314,4 +329,79 @@ private function getCountryName($countryId) $this->_countryInformation->getCountryInfo($countryId)->getFullNameLocale() : $countryId; } + + /** + * Set PagSeguro recovery shopping cart value + * + * @return void + */ + private function setShoppingCartRecovery() + { + if ($this->_scopeConfig->getValue('payment/pagseguro/shopping_cart_recovery')=== true) { + $this->_paymentRequest->addParameter()->withParameters('enableRecovery', 'true'); + } else { + $this->_paymentRequest->addParameter()->withParameters('enableRecovery', 'false'); + } + } + + /** + * Get the discount configuration for PagSeguro store configurarion and + * set in the payment request the discount amount for every payment method configured + * + * @return void + */ + private function setPagSeguroDiscountsByPaymentMethod() + { + $storeId = \Magento\Store\Model\ScopeInterface::SCOPE_STORE; + if ($this->_scopeConfig->getValue('payment/pagseguro_default_lightbox/discount_credit_card', $storeId)=== 1) { + $creditCard = (double)$this->_scopeConfig->getValue('payment/pagseguro_default_lightbox/discount_credit_card_value', $storeId); + if ($creditCard && $creditCard !== 0.00) { + $this->_paymentRequest->addPaymentMethod()->withParameters( + \PagSeguro\Enum\PaymentMethod\Group::CREDIT_CARD, + \PagSeguro\Enum\PaymentMethod\Config\Keys::DISCOUNT_PERCENT, + $creditCard + ); + } + } + if ($this->_scopeConfig->getValue('payment/pagseguro_default_lightbox/discount_online_debit', $storeId)=== 1) { + $eft = (double)$this->_scopeConfig->getValue('payment/pagseguro_default_lightbox/discount_online_debit_value', $storeId); + if ($eft && $eft !== 0.00) { + $this->_paymentRequest->addPaymentMethod()->withParameters( + \PagSeguro\Enum\PaymentMethod\Group::EFT, + \PagSeguro\Enum\PaymentMethod\Config\Keys::DISCOUNT_PERCENT, + $eft + ); + } + } + if ($this->_scopeConfig->getValue('payment/pagseguro_default_lightbox/discount_boleto', $storeId)=== 1) { + $boleto = (double)$this->_scopeConfig->getValue('payment/pagseguro_default_lightbox/discount_boleto_value', $storeId); + if ($boleto && $boleto !== 0.00) { + $this->_paymentRequest->addPaymentMethod()->withParameters( + \PagSeguro\Enum\PaymentMethod\Group::BOLETO, + \PagSeguro\Enum\PaymentMethod\Config\Keys::DISCOUNT_PERCENT, + $boleto + ); + } + } + if ($this->_scopeConfig->getValue('payment/pagseguro_default_lightbox/discount_deposit_account', $storeId)) { + $deposit = (double)$this->_scopeConfig->getValue('payment/pagseguro_default_lightbox/discount_deposit_account_value', $storeId); + if ($deposit && $deposit !== 0.00) { + $this->_paymentRequest->addPaymentMethod()->withParameters( + \PagSeguro\Enum\PaymentMethod\Group::DEPOSIT, + \PagSeguro\Enum\PaymentMethod\Config\Keys::DISCOUNT_PERCENT, + $deposit + ); + } + } + if ($this->_scopeConfig->getValue('payment/pagseguro_default_lightbox/discount_balance', $storeId)) { + $balance = (double)$this->_scopeConfig->getValue('payment/pagseguro_default_lightbox/discount_balance_value', $storeId); + if ($balance && $balance !== 0.00) { + $this->_paymentRequest->addPaymentMethod()->withParameters( + \PagSeguro\Enum\PaymentMethod\Group::BALANCE, + \PagSeguro\Enum\PaymentMethod\Config\Keys::DISCOUNT_PERCENT, + $balance + ); + } + } + } } diff --git a/Model/Transactions/Method.php b/Model/Transactions/Method.php index a0da0fa..7878e77 100644 --- a/Model/Transactions/Method.php +++ b/Model/Transactions/Method.php @@ -50,11 +50,12 @@ protected function sanitizeConfig($data) protected function getTransactions($page = null) { //check if has a page, if doesn't have one then start at the first. - if (is_null($page)) $page = 1; + if ($page===null) $page = 1; try { + //check if is the first step, if is just add the response object to local var - if (is_null($this->_PagSeguroPaymentList)) { + if ($this->_PagSeguroPaymentList===null) { $this->_PagSeguroPaymentList = $this->requestPagSeguroPayments($page); } else { $response = $this->requestPagSeguroPayments($page); @@ -78,6 +79,102 @@ protected function getTransactions($page = null) return $this->_PagSeguroPaymentList; } + /** + * Get all transactions where there is a pagseguro transaction code + * @return array + * @throws \Exception + */ + public function searchTransactions() + { + try { + $connection = $this->getConnection(); + $select = $connection->select() + ->from( ['order' => 'sales_order'], ['status', 'created_at', 'increment_id', 'store_id', 'entity_id'] ) + ->join( ['ps' => 'pagseguro_orders'], 'order.entity_id = ps.order_id') + ->where('ps.transaction_code !== ?', '') + ->order('order.created_at DESC'); + + if (!is_null($this->_session->getData('store_id'))) { + $select = $select->where('order.store_id = ?', $this->_session->getData('store_id')); + } + + if ($this->_scopeConfig->getValue('payment/pagseguro/environment')) { + $select = $select->where('ps.environment = ?', $this->_scopeConfig->getValue('payment/pagseguro/environment')); + } + + if (!empty($this->_idMagento)) { + $select = $select->where('order.increment_id = ?', $this->_idMagento); + } + + if (!empty($this->_idPagseguro)) { + $select = $select->where('ps.transaction_code = ?', $this->_idPagseguro); + } + + if (!empty($this->_status)) { + $select = $this->getStatusFromPaymentKey($this->_status)=== 'partially_refunded' + ? $select->where('ps.partially_refunded = ?', 1) + : $select->where('order.status = ?', $this->getStatusFromPaymentKey($this->_status)); + } + + if (!empty($this->_dateBegin) && !empty($this->_dateEnd)) { + $startDate = date('Y-m-d H:i:s', strtotime(str_replace("/", "-", $this->_dateBegin))); + $endDate = date('Y-m-d'.' 23:59:59', strtotime(str_replace("/", "-", $this->_dateEnd))); + $select = $select->where('order.created_at >= ?', $startDate)->where('order.created_at <= ?', $endDate); + } + + $connection->prepare($select); + return $connection->fetchAll($select); + } catch (\Exception $exception) { + throw $exception; + } + } + + /** Get and formats transaction details + * @param $transactionCode + * @throws Exception + */ + public function getDetailsTransaction($transactionCode) + { + $this->_detailsTransactionByCode = $this->getTransactionsByCode($transactionCode); + + if(!empty($this->_detailsTransactionByCode)){ + $order = $this->decryptOrderById($this->_detailsTransactionByCode); + + if ($this->getStoreReference()=== $this->decryptReference($this->_detailsTransactionByCode)) { + if ($this->_detailsTransactionByCode->getStatus()=== $this->getKeyFromOrderStatus($order->getStatus())) { + $this->_detailsTransactionByCode = $this->buildDetailsTransaction(); + $this->_needConciliate = false; + } + } + } + } + + /** + * Request PagSeguroTransaction details by code + * @param $code + * + * @return null|object + * @throws Exception + */ + public function getTransactionsByCode($code) + { + $this->_library->setEnvironment(); + $this->_library->setCharset(); + $this->_library->setLog(); + + $response = null; + try { + $response = \PagSeguro\Services\Transactions\Search\Code::search( + $this->_library->getPagSeguroCredentials(), + $code + ); + + return $response; + } catch (Exception $e) { + throw new Exception($e->getMessage()); + } + } + /** * Request all PagSeguroTransaction in this _date interval * @@ -167,9 +264,10 @@ protected function getStatusString($status) * @param $order * @return mixed */ - protected function getKeyFromOrderStatus($order) + protected function getKeyFromOrderStatus($status) { - return \UOL\PagSeguro\Helper\Data::getKeyFromStatus($order->getStatus()); + $param = is_object($status) ? $status->getStatus() : $status; + return \UOL\PagSeguro\Helper\Data::getKeyFromStatus($param); } /** @@ -182,12 +280,15 @@ protected function formatPagSeguroStatus($payment) } /** - * @param $order + * @param string $status + * @param integer $isPartiallyRefunded * @return bool|string */ - protected function formatMagentoStatus($order) + protected function formatMagentoStatus($status, $isPartiallyRefunded = 0) { - return $this->getStatusString($this->getKeyFromOrderStatus($order)); + return $isPartiallyRefunded + ? $this->getStatusString($this->getKeyFromOrderStatus($status)) . ' (estornada parcialmente)' + : $this->getStatusString($this->getKeyFromOrderStatus($status)); } /** @@ -205,7 +306,8 @@ protected function formatMagentoId($order) */ protected function formatDate($order) { - return date("d/m/Y H:i:s", strtotime($order->getCreatedAt())); + $param = is_object($order) ? $order->getCreatedAt() : $order; + return date("d/m/Y H:i:s", strtotime($param)); } /** @@ -283,6 +385,49 @@ private function getPrefixTableName($table) return $this->_resource->getTableName($table); } + /** + * Update column 'partially_refunded' the `pagseguro_orders` table + * + * @param int $orderId + * @return void + */ + protected function updatePartiallyRefundedPagSeguro($orderId) + { + $this->getConnection() + ->query(sprintf( + "UPDATE `%s` SET partially_refunded = 1 WHERE entity_id='%s'", + $this->getPrefixTableName('pagseguro_orders'), + $orderId + )); + } + + /** + * Get all pagseguro partially refunded orders id + * + * @return array + */ + protected function getPartiallyRefundedOrders() + { + $pagseguroOrdersIdArray = array(); + + $connection = $this->getConnection(); + $select = $connection->select() + ->from( ['ps' => $this->getPrefixTableName('pagseguro_orders')], ['order_id'] ) + ->where('ps.partially_refunded = ?', '1'); + + if ($this->_scopeConfig->getValue('payment/pagseguro/environment')) { + $select = $select->where('ps.environment = ?', $this->_scopeConfig->getValue('payment/pagseguro/environment')); + } + + $connection->prepare($select); + + foreach ($connection->fetchAll($select) as $value) { + $pagseguroOrdersIdArray[] = $value['order_id']; + } + + return $pagseguroOrdersIdArray; + } + /** * @param $order * @param $payment @@ -303,4 +448,159 @@ abstract public function execute($data); * @return mixed */ abstract protected function build($payment, $order); + + /** + * Build and format the transaction data for the listing + * @return array + */ + public function buildDetailsTransaction() + { + return array( + 'date' => $this->formatDate($this->_detailsTransactionByCode->getDate()), + 'code' => $this->_detailsTransactionByCode->getCode(), + 'reference' => $this->_detailsTransactionByCode->getReference(), + 'type' => \UOL\PagSeguro\Helper\Data::getTransactionTypeName($this->_detailsTransactionByCode->getType()), + 'status' => \UOL\PagSeguro\Helper\Data::getPaymentStatusToString($this->_detailsTransactionByCode->getStatus()), + 'lastEventDate' => $this->formatDate($this->_detailsTransactionByCode->getLastEventDate()), + 'installmentCount' => $this->_detailsTransactionByCode->getInstallmentCount(), + 'cancelationSource' => \UOL\PagSeguro\Helper\Data::getTitleCancellationSourceTransaction($this->_detailsTransactionByCode->getCancelationSource()), + 'discountAmount' => $this->_detailsTransactionByCode->getDiscountAmount(), + 'escrowEndDate' => $this->formatDate($this->_detailsTransactionByCode->getEscrowEndDate()), + 'extraAmount' => $this->_detailsTransactionByCode->getExtraAmount(), + 'feeAmount' => $this->_detailsTransactionByCode->getFeeAmount(), + 'grossAmount' => $this->_detailsTransactionByCode->getGrossAmount(), + 'netAmount' => $this->_detailsTransactionByCode->getNetAmount(), + 'creditorFees' => $this->prepareCreditorFees(), + 'itemCount' => $this->_detailsTransactionByCode->getItemCount(), + 'items' => $this->prepareItems(), + 'paymentMethod' => $this->preparePaymentMethod(), + 'sender' => $this->prepareSender(), + 'shipping' => $this->prepareShipping(), + 'paymentLink' => $this->_detailsTransactionByCode->getPaymentLink(), + 'promoCode' => $this->_detailsTransactionByCode->getPromoCode() + ); + } + + /** + * Format transaction CreditorFees + * @return array|string + */ + private function prepareCreditorFees() + { + $creditorFees = ""; + if(!empty($this->_detailsTransactionByCode->getCreditorFees())) + { + $creditorFees = array( + 'intermediationRateAmount' => $this->_detailsTransactionByCode->getCreditorFees()->getIntermediationRateAmount(), + 'intermediationFeeAmount' => $this->_detailsTransactionByCode->getCreditorFees()->getIntermediationFeeAmount(), + 'installmentFeeAmount' => $this->_detailsTransactionByCode->getCreditorFees()->getInstallmentFeeAmount(), + 'operationalFeeAmount' => $this->_detailsTransactionByCode->getCreditorFees()->getOperationalFeeAmount(), + 'commissionFeeAmount' => $this->_detailsTransactionByCode->getCreditorFees()->getCommissionFeeAmount() + ); + } + return $creditorFees; + } + + /** + * Format transaction Items + * @return array + */ + private function prepareItems() + { + $itens = array(); + + if($this->_detailsTransactionByCode->getItemCount() > 0) { + foreach ($this->_detailsTransactionByCode->getItems() as $item) + { + $itens[] = array( + 'id' => $item->getId(), + 'description' => $item->getDescription(), + 'quantity' => $item->getQuantity(), + 'amount' => $item->getAmount(), + 'weight' => $item->getWeight(), + 'shippingCost' => $item->getShippingCost() + ); + } + } + return $itens; + } + + /** + * Format transaction PaymentMethod + * @return array|string + */ + private function preparePaymentMethod() + { + $paymentMethod = ""; + + if(!empty($this->_detailsTransactionByCode->getPaymentMethod())) + { + $paymentMethod = array( + 'code' => $this->_detailsTransactionByCode->getPaymentMethod()->getCode(), + 'type' => $this->_detailsTransactionByCode->getPaymentMethod()->getType(), + 'titleType' => \UOL\PagSeguro\Helper\Data::getTitleTypePaymentMethod($this->_detailsTransactionByCode->getPaymentMethod()->getType()), + 'titleCode' => \UOL\PagSeguro\Helper\Data::getTitleCodePaymentMethod($this->_detailsTransactionByCode->getPaymentMethod()->getCode()) + ); + } + return $paymentMethod; + } + + /** + * Format transaction Sender + * @return array + */ + private function prepareSender() + { + $documents = array(); + if(count($this->_detailsTransactionByCode->getSender()->getDocuments()) > 0) { + foreach ($this->_detailsTransactionByCode->getSender()->getDocuments() as $doc) + { + $documents[] = array( + 'type' => $doc->getType(), + 'identifier' => $doc->getIdentifier() + ); + } + } + + $sender = array(); + if(!empty($this->_detailsTransactionByCode->getSender())){ + $sender = array( + 'name' => $this->_detailsTransactionByCode->getSender()->getName(), + 'email' => $this->_detailsTransactionByCode->getSender()->getEmail(), + 'phone' => array( + 'areaCode' => $this->_detailsTransactionByCode->getSender()->getPhone()->getAreaCode(), + 'number' => $this->_detailsTransactionByCode->getSender()->getPhone()->getNumber() + ), + 'documents' => $documents + ); + } + return $sender; + } + + /** + * Format transaction Shipping + * @return array + */ + private function prepareShipping() + { + $shipping = array(); + if(!empty($this->_detailsTransactionByCode->getShipping())){ + $shipping = array( + 'addres' => array( + 'street' => $this->_detailsTransactionByCode->getShipping()->getAddress()->getStreet(), + 'number' => $this->_detailsTransactionByCode->getShipping()->getAddress()->getNumber(), + 'complement' => $this->_detailsTransactionByCode->getShipping()->getAddress()->getComplement(), + 'district' => $this->_detailsTransactionByCode->getShipping()->getAddress()->getDistrict(), + 'postalCode' => $this->_detailsTransactionByCode->getShipping()->getAddress()->getPostalCode(), + 'city' => $this->_detailsTransactionByCode->getShipping()->getAddress()->getCity(), + 'state' => $this->_detailsTransactionByCode->getShipping()->getAddress()->getState(), + 'country' => $this->_detailsTransactionByCode->getShipping()->getAddress()->getCountry() + ), + 'type' => $this->_detailsTransactionByCode->getShipping()->getType()->getType(), + 'cost' => $this->_detailsTransactionByCode->getShipping()->getCost()->getCost() + ); + } + return $shipping; + } + } \ No newline at end of file diff --git a/Model/Transactions/Methods/Abandoned.php b/Model/Transactions/Methods/Abandoned.php index f3f0fab..4724944 100755 --- a/Model/Transactions/Methods/Abandoned.php +++ b/Model/Transactions/Methods/Abandoned.php @@ -106,7 +106,6 @@ class Abandoned extends Method */ public function __construct( \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfigInterface, - \Magento\Framework\App\ResourceConnection $resourceConnection, \Magento\Framework\Mail\Template\TransportBuilder $transportBuilder, \Magento\Framework\Model\ResourceModel\Db\Context $context, \Magento\Backend\Model\Session $session, @@ -189,7 +188,7 @@ public function request() date_default_timezone_set('UTC'); $order = \UOL\PagSeguro\Helper\Data::getReferenceDecryptOrderID($payment->getReference()); $order = $this->_order->load($order); - if ($this->getStoreReference() == \UOL\PagSeguro\Helper\Data::getReferenceDecrypt( + if ($this->getStoreReference()=== \UOL\PagSeguro\Helper\Data::getReferenceDecrypt( $payment->getReference()) ) { if (!is_null($this->_session->getData('store_id'))) { @@ -272,11 +271,11 @@ protected function details($order, $payment, $options) private function getPagSeguroAbandoned($page = null) { //check if has a page, if doesn't have one then start at the first. - if (is_null($page)) $page = 1; + if ($page===null) $page = 1; try { //check if is the first step, if is just add the response object to local var - if (is_null($this->_PagSeguroPaymentList)) { + if ($this->_PagSeguroPaymentList===null) { $this->_PagSeguroPaymentList = $this->requestPagSeguroAbandoned($page); } else { @@ -402,7 +401,7 @@ private function abandonedIntervalToDate($date) private function abandonedRecoveryUrl($recoveryCode) { - if (strtolower($this->_library->getEnvironment()) == "sandbox") { + if (strtolower($this->_library->getEnvironment())=== "sandbox") { return 'https://sandbox.pagseguro.uol.com.br/checkout/v2/resume.html?r=' . $recoveryCode; } return 'https://pagseguro.uol.com.br/checkout/v2/resume.html?r=' . $recoveryCode; @@ -463,4 +462,4 @@ private function setSent($orderId, $sent) $mapsDeleteQuery = "UPDATE {$tableName} SET sent={$sent} WHERE order_id={$orderId}"; $connection->query($mapsDeleteQuery); } -} \ No newline at end of file +} diff --git a/Model/Transactions/Methods/Cancellation.php b/Model/Transactions/Methods/Cancellation.php index c13bac2..33d164e 100755 --- a/Model/Transactions/Methods/Cancellation.php +++ b/Model/Transactions/Methods/Cancellation.php @@ -94,7 +94,6 @@ class Cancellation extends Method */ public function __construct( \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfigInterface, - \Magento\Framework\App\ResourceConnection $resourceConnection, \Magento\Framework\Model\ResourceModel\Db\Context $context, \Magento\Backend\Model\Session $session, \Magento\Sales\Model\Order $order, @@ -193,7 +192,7 @@ private function addStatusToOrder($id, $status) */ private function doCancel($config) { - if ($this->requestCancel($config)->getResult() == "OK") + if ($this->requestCancel($config)->getResult()=== "OK") return true; throw new \Exception("an error occurred."); } @@ -315,7 +314,7 @@ protected function details($order, $payment, $options) */ private function checkConciliation($payment, $order) { - if ($order->getStatus() == $this->getStatusFromPaymentKey($payment->getStatus())) + if ($order->getStatus()=== $this->getStatusFromPaymentKey($payment->getStatus())) return true; return false; } @@ -346,7 +345,7 @@ private function compareStatus($order, $payment) */ private function compareStore($payment) { - if ($this->getStoreReference() != $this->decryptReference($payment)) + if ($this->getStoreReference() !== $this->decryptReference($payment)) return false; return true; } diff --git a/Model/Transactions/Methods/Conciliation.php b/Model/Transactions/Methods/Conciliation.php index 5e7781f..a95d589 100755 --- a/Model/Transactions/Methods/Conciliation.php +++ b/Model/Transactions/Methods/Conciliation.php @@ -94,7 +94,6 @@ class Conciliation extends Method */ public function __construct( \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfigInterface, - \Magento\Framework\App\ResourceConnection $resourceConnection, \Magento\Framework\Model\ResourceModel\Db\Context $context, \Magento\Backend\Model\Session $session, \Magento\Sales\Model\Order $order, @@ -260,7 +259,7 @@ protected function details($order, $payment, $options = null) */ private function compareStore($payment) { - if ($this->getStoreReference() != $this->decryptReference($payment)) + if ($this->getStoreReference() !== $this->decryptReference($payment)) return false; return true; } @@ -274,7 +273,7 @@ private function compareStore($payment) */ private function compareStatus($order, $payment) { - if ($order->getStatus() == $this->getStatusFromPaymentKey($payment->getStatus())) + if ($order->getStatus()=== $this->getStatusFromPaymentKey($payment->getStatus())) return false; return true; } diff --git a/Model/Transactions/Methods/Refund.php b/Model/Transactions/Methods/Refund.php index 25eaf18..feb8488 100755 --- a/Model/Transactions/Methods/Refund.php +++ b/Model/Transactions/Methods/Refund.php @@ -94,7 +94,6 @@ class Refund extends Method */ public function __construct( \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfigInterface, - \Magento\Framework\App\ResourceConnection $resourceConnection, \Magento\Framework\Model\ResourceModel\Db\Context $context, \Magento\Backend\Model\Session $session, \Magento\Sales\Model\Order $order, @@ -130,21 +129,24 @@ public function __construct( * Refund one transaction * * @param $data + * @param $value * @return bool * @throws \Exception */ - public function execute($data) { - + public function execute($data, $value = null) { try { $config = $this->sanitizeConfig($data); + if ($value !== null) + $config->value = number_format(floatval($value), 2, '.', ''); $this->isConciliate($config); if (!$this->doRefund($config)) - throw new \Exception('impossible to refund'); + throw new \Exception('impossible to refund'); $this->doUpdates($config); return true; } catch (\Exception $exception) { - throw $exception; + $error = simplexml_load_string($exception->getMessage()); + throw new \Exception((string)$error->error->code); } } @@ -164,9 +166,17 @@ private function isConciliate($config) private function doUpdates($config) { try { - $this->addStatusToOrder($config->order_id, 'pagseguro_devolvida'); - $this->updateSalesOrder($config->order_id, $config->pagseguro_id); - $this->updatePagSeguroOrders($config->order_id, $config->pagseguro_id); + /* if have refund value is an partially refund, so the status should be keeped */ + if ($config->value) { + $comment = 'Estornado valor de R$' . $config->value . ' do seu pedido.'; + $this->setPartiallyRefundedStatus($config->order_id); + $this->notifyCustomer($config->order_id, $config->pagseguro_status, $comment); + } else { + $this->addStatusToOrder($config->order_id, 'pagseguro_devolvida'); + $this->updateSalesOrder($config->order_id, $config->pagseguro_id); + $this->updatePagSeguroOrders($config->order_id, $config->pagseguro_id); + } + unset($order); } catch (\Exception $exception) { throw $exception; @@ -195,7 +205,7 @@ private function addStatusToOrder($id, $status) */ private function doRefund($config) { - if ($this->requestRefund($config)->getResult() == "OK") + if ($this->requestRefund($config)->getResult()=== "OK") return true; throw new \Exception("an error occurred"); } @@ -216,7 +226,8 @@ private function requestRefund($config) try { return \PagSeguro\Services\Transactions\Refund::create( $this->_library->getPagSeguroCredentials(), - $config->pagseguro_id + $config->pagseguro_id, + $config->value ); } catch (\Exception $exception) { throw $exception; @@ -233,9 +244,15 @@ public function request() { $this->getTransactions(); if (! is_null($this->_PagSeguroPaymentList->getTransactions())) { + $partiallyRefundedOrdersArray = $this->getPartiallyRefundedOrders(); + foreach ($this->_PagSeguroPaymentList->getTransactions() as $payment) { - if (! $this->addPayment($this->decryptOrderById($payment), $payment)) - continue; + $order = $this->decryptOrderById($payment); + + if (!in_array($order->getId(), $partiallyRefundedOrdersArray)) { + if (! $this->addPayment($this->decryptOrderById($payment), $payment)) + continue; + } } } return $this->_arrayPayments; @@ -285,7 +302,8 @@ private function toArray($payment, $order, $conciliate = false) 'magento_status' => $this->formatMagentoStatus($order), 'pagseguro_id' => $payment->getCode(), 'order_id' => $order->getId(), - 'details' => $this->details($order, $payment, ['conciliate' => $conciliate]) + 'details' => $this->details($order, $payment, ['conciliate' => $conciliate]), + 'value' => $payment->getGrossAmount(), ]; } @@ -304,7 +322,8 @@ protected function details($order, $payment, $options) 'order_id' => $order->getId(), 'pagseguro_status' => $payment->getStatus(), 'pagseguro_id' => $payment->getCode(), - 'needConciliate' => $options['conciliate'] + 'needConciliate' => $options['conciliate'], + 'value' => null ]) ); } @@ -318,7 +337,7 @@ protected function details($order, $payment, $options) */ private function checkConciliation($payment, $order) { - if ($order->getStatus() == $this->getStatusFromPaymentKey($payment->getStatus())) + if ($order->getStatus()=== $this->getStatusFromPaymentKey($payment->getStatus())) return true; return false; } @@ -332,14 +351,14 @@ private function checkConciliation($payment, $order) */ private function compareStatus($order, $payment) { - if (! (in_array($order->getStatus(), [ + if ((in_array($order->getStatus(), [ $this->getStatusFromPaymentKey(3), $this->getStatusFromPaymentKey(4), $this->getStatusFromPaymentKey(5), - ]) || in_array($payment->getStatus(), [3, 4, 5]))) { - return false; + ])=== 1 && in_array($payment->getStatus(), [3, 4, 5])=== 1)) { + return true; } - return true; + return false; } /** @@ -350,7 +369,7 @@ private function compareStatus($order, $payment) */ private function compareStore($payment) { - if ($this->getStoreReference() != $this->decryptReference($payment)) + if ($this->getStoreReference() !== $this->decryptReference($payment)) return false; return true; } @@ -367,4 +386,28 @@ private function hasOrder($order) return false; return true; } + + /** + * Updates respective order partially refunded status to 1 in pagseguro_orders table + * + * @param string $orderId + * @return void + */ + private function setPartiallyRefundedStatus($orderId) + { + $this->updatePartiallyRefundedPagSeguro($orderId); + } + + /** + * @param $orderId + * @param $orderStatus + * @param $comment + */ + public function notifyCustomer($orderId, $orderStatus, $comment = null) + { + $notify = true; + $order = $this->_order->load($orderId); + $order->addStatusToHistory($this->getStatusFromPaymentKey($orderStatus), $comment, $notify); + $order->save(); + } } diff --git a/Model/Transactions/Methods/Transactions.php b/Model/Transactions/Methods/Transactions.php new file mode 100755 index 0000000..0d78141 --- /dev/null +++ b/Model/Transactions/Methods/Transactions.php @@ -0,0 +1,221 @@ +_scopeConfig = $scopeConfigInterface; + /** @var \Magento\Framework\App\ResourceConnection _resource */ + $this->_resource = $resourceConnection; + /** @var \Magento\Backend\Model\Session _session */ + $this->_session = $session; + /** @var \Magento\Sales\Model\Order _order */ + $this->_order = $order; + /** @var \UOL\PagSeguro\Helper\Library _library */ + $this->_library = $library; + /** @var \UOL\PagSeguro\Helper\Crypt _crypt */ + $this->_crypt = $crypt; + /** @var int _idMagento */ + $this->_idMagento = $idMagento; + /** @var int _idPagseguro */ + $this->_idPagseguro = $idPagseguro; + /** @var int _dateBegin */ + $this->_dateBegin = $dateBegin; + /** @var int _dateEnd */ + $this->_dateEnd = $dateEnd; + /** @var int _status */ + $this->_status = $status; + /** @var \Magento\Sales\Model\ResourceModel\Grid _salesGrid */ + $this->_salesGrid = new \Magento\Sales\Model\ResourceModel\Grid( + $context, + 'pagseguro_orders', + 'sales_order_grid', + 'order_id' + ); + } + + /** + * Get all transactions and return formatted data + * + * @return array + * @throws \Exception + */ + public function request() + { + $transactions = $this->searchTransactions(); + + if(count($transactions) > 0) { + foreach ($transactions as $transaction) { + $this->_arrayTransactions[] = array( + 'date' => $this->formatDate($transaction['created_at']), + 'magento_id' => $transaction['increment_id'], + 'pagseguro_id' => $transaction['transaction_code'], + 'environment' => $transaction['environment'], + 'magento_status' => $this->formatMagentoStatus($transaction['status'], $transaction['partially_refunded']), + 'order_id' => $transaction['entity_id'] + ); + } + } + return $this->_arrayTransactions; + } + + /** + * Get details transactions + * + * @param $data + * @return array + * @throws \Exception + */ + public function execute($data) { + + $this->getDetailsTransaction(str_replace('-', '', $data)); + + if(!empty($this->_detailsTransactionByCode) && $this->_needConciliate){ + throw new \Exception('need to conciliate'); + } + + if (empty($this->_detailsTransactionByCode)) { + throw new \Exception('empty'); + } + return $this->_detailsTransactionByCode; + } + + + /** + * Build data for dataTable + * + * @param $payment + * @param $order + * @return array + */ + protected function build($payment, $order) + { + throw new NotImplementedException(); + } + + /** + * Get data for details + * + * @param $order + * @param $payment + * @param $options + * @return string + */ + protected function details($order, $payment, $options) + { + throw new NotImplementedException(); + } + +} diff --git a/Observer/CreatePagSeguroOrder.php b/Observer/CreatePagSeguroOrder.php index 1f40f66..3ab6970 100644 --- a/Observer/CreatePagSeguroOrder.php +++ b/Observer/CreatePagSeguroOrder.php @@ -97,7 +97,7 @@ public function execute(\Magento\Framework\Event\Observer $observer) $order = $observer->getEvent()->getOrder(); //verify pagseguro transaction - if ($order->getStatus() == 'pagseguro_iniciado') { + if ($order->getStatus()=== 'pagseguro_iniciado') { $orderId = $order->getId(); $environment = $this->_scopeConfig->getValue('payment/pagseguro/environment'); diff --git a/README.md b/README.md index 84d236d..f57c89f 100644 --- a/README.md +++ b/README.md @@ -1,193 +1,182 @@ -Módulo de integração PagSeguro para Magento 2.x -==================================================== +# Módulo de integração PagSeguro para Magento 2.x +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/4b4b0552eb414875976352bf398bb449)](https://app.codacy.com/gh/pagseguro/pagseguro-modulo-magento-v2?utm_source=github.com&utm_medium=referral&utm_content=pagseguro/pagseguro-modulo-magento-v2&utm_campaign=Badge_Grade) [![Code Climate](https://codeclimate.com/github/pagseguro/magento2/badges/gpa.svg)](https://codeclimate.com/github/pagseguro/magento2) --- -Descrição ---------- + +## Descrição + --- + Com o módulo instalado e configurado, você pode pode oferecer o PagSeguro como opção de pagamento em sua loja. O módulo utiliza as seguintes funcionalidades que o PagSeguro oferece na forma de APIs: - - Integração com a [API de Pagamentos] - - Integração com a [API de Notificações] +- Integração com a [API de Pagamentos] +- Integração com a [API de Notificações] +## Requisitos -Requisitos ----------- --- - - [Magento] Community 2.0.8 | 2.1.0 até a versão 2.1.9 - - [PHP] 5.5.0+ - - [SPL] - - [cURL] - - [DOM] +- [Magento] Community 2.0.8 | 2.1.0 até a versão 2.1.9 +- [Versões Magento] Para versões anteriores, usar outras tags + +- [PHP] 5.5+ + +- [SPL] +- [cURL] +- [DOM] + +## Instalação -Instalação ------------ > É altamente recomendado que você tenha um ambiente de testes para validar alterações e atualizações antes de atualizar sua loja em produção. É recomendado também que seja feito um **backup** da sua loja e informações importantes antes de executar qualquer procedimento de atualização/instalação. Navegue até o diretório raíz da sua instalação do Magento 2 e siga os seguintes passos: > A instalação do módulo é feita utilizando o Composer. Para baixar e instalar o Composer no seu ambiente acesse https://getcomposer.org/download/ e caso tenha dúvidas de como utilizá-lo consulte a [documentação oficial do Composer](https://getcomposer.org/doc/). -1. Instale via packagist - - ```composer require pagseguro/magento2``` - - Neste momento, podem ser solicitadas suas credenciais de autenticação do Magento. Caso tenha alguma dúvida, há uma descrição de como proceder nesse [link da documentação oficial](http://devdocs.magento.com/guides/v2.0/install-gde/prereq/connect-auth.html). +1. Instale via packagist + - `composer require pagseguro/magento2` + - Neste momento, podem ser solicitadas suas credenciais de autenticação do Magento. Caso tenha alguma dúvida, há uma descrição de como proceder nesse [link da documentação oficial](http://devdocs.magento.com/guides/v2.0/install-gde/prereq/connect-auth.html). 2. Execute os comandos: - - ```php bin/magento setup:upgrade``` - - ```php bin/magento setup:static-content:deploy``` ou ```php bin/magento setup:static-content:deploy pt_BR```, de acordo com as configurações da sua loja. -3. Cheque e, caso necessário, configure as permissões corretas para seus diretórios. Por exemplo, para configrar a permissão 777 para as pastas var/ pub/ execute: - - ```chmod 777 -R var/ pub/``` -4. Pode ser necessário atualizar o cache da sua loja ao finalizar o processo. -5. Acesse a seção do PagSeguro através da interface administrativa da sua loja e configure suas credenciais e meios de pagamento. + - `php bin/magento setup:upgrade` + - `php bin/magento setup:static-content:deploy` ou `php bin/magento setup:static-content:deploy pt_BR`, de acordo com as configurações da sua loja. + +3. Dê permissões as pastas var/ pub/ + - `chmod -R 777 var/ pub/` + +## Atualização -Atualização ------------ > É altamente recomendado que você tenha um ambiente de testes para validar alterações e atualizações antes de atualizar sua loja em produção. É recomendado também que seja feito um **backup** da sua loja e informações importantes antes de executar qualquer procedimento de atualização/instalação. A atualização do módulo do PagSeguro é feita através do **composer** e pode ser feita de diversas maneiras, de acordo com suas preferências. Uma forma é através dos comandos: -1. ```composer update pagseguro/magento2``` -2. ```composer update pagseguro/pagseguro-php-sdk``` -3. ```php bin/magento setup:upgrade``` -4. ```php bin/magento setup:static-content:deploy``` ou ```php bin/magento setup:static-content:deploy pt_BR```, de acordo com as configurações da sua loja. + +1. `composer update pagseguro/magento2` +2. `composer update pagseguro/pagseguro-php-sdk` +3. `php bin/magento setup:upgrade` +4. `php bin/magento setup:static-content:deploy` ou `php bin/magento setup:static-content:deploy pt_BR`, de acordo com as configurações da sua loja. 5. Cheque e, caso necessário, configure as permissões corretas para seus diretórios. 6. Pode ser necessário atualizar o cache da sua loja ao finalizar o processo. -5. Acesse a seção do PagSeguro através da interface administrativa da sua loja, confira as informações e configurações do PagSeguro e seus meios de pagamento e clique no botão para salvar. +7. Acesse a seção do PagSeguro através da interface administrativa da sua loja, confira as informações e configurações do PagSeguro e seus meios de pagamento e clique no botão para salvar. + +**Observações** -**Observações** -- Em alguns casos, o Magento não atualiza os arquivos estáticos gerados, podendo ser necessário atualizar os mesmos via interface administrativa, comandos do terminal ou removendo diretamente conteúdo da pasta *pub/static/frontend/Magento/seu_tema/seu_idioma/UOL_PagSeguro*. -- Em seguida, executar novamente o comando ```php bin/magento setup:static-content:deploy``` ou ```bin/magento setup:static-content:deploy pt_BR```, de acordo com as configurações da sua loja. +- Em alguns casos, o Magento não atualiza os arquivos estáticos gerados, podendo ser necessário atualizar os mesmos via interface administrativa, comandos do terminal ou removendo diretamente conteúdo da pasta _pub/static/frontend/Magento/seu_tema/seu_idioma/UOL_PagSeguro_. +- Em seguida, executar novamente o comando `php bin/magento setup:static-content:deploy` ou `bin/magento setup:static-content:deploy pt_BR`, de acordo com as configurações da sua loja. + +## Configuração -Configuração ------------- --- + Para acessar e configurar o módulo acesse o menu PagSeguro -> Configurações. As opções disponíveis estão descritas abaixo. - ------------------------- - **Configurações Gerais** - - - **ambiente**: especifica em que ambiente as transações serão feitas *(produção/sandbox)*. - - **e-mail**: e-mail cadastrado no PagSeguro. - - **token**: token cadastrado no PagSeguro. - - **url de redirecionamento**: ao final do fluxo de pagamento no PagSeguro, seu cliente será redirecionado automaticamente para a página de confirmação em sua loja ou então para a URL que você informar neste campo. Para ativar o redirecionamento ao final do pagamento é preciso ativar o serviço de [Pagamentos via API]. Obs.: Esta URL é informada automaticamente e você só deve alterá-la caso deseje que seus clientes sejam redirecionados para outro local. - - **url de notificação**: sempre que uma transação mudar de status, o PagSeguro envia uma notificação para sua loja. **O valor padrão que deve ser utilizado pelo módulo é: http://www.minhaloja.com.br/index.php/pagseguro/notification/response** - - *Observação: Esta URL só deve ser alterada caso você deseje receber as notificações em outro local.* - - **charset**: codificação do seu sistema (ISO-8859-1 ou UTF-8). - - **ativar log**: ativa/desativa a geração de logs. - - **diretório**: informe o local e nome do arquivo a partir da raíz de instalação do Magento onde se deseja criar o arquivo de log. Ex.: var/log/pagseguro.log. - - *Por padrão o módulo virá configurado para salvar o arquivo de log em var/log/pagseguro.log*. - - **listar transações abandonadas?**: ativa/desativa a pesquisa de transações que foram abandonadas no checkout do PagSeguro. - - **transações -> abandonadas**: permite consultar as transações que foram abandonadas nos últimos 10 dias, desta forma você pode enviar emails de recuperação de venda. O e-mail conterá um link que redirecionará o comprador para o fluxo de pagamento, exatamente no ponto onde ele parou. - - **listar parcelamento**: Habilita a exibição de uma listagem de parcelas na tela de visualização do produto. (Irá exibir o maior parcelamento disponível para o produto na tela de exibição do mesmo) - - ------------------------- - **Configurar Tipos de Checkout** - Nesta seção você irá configurar os meios de pagamento do PagSeguro que deseja disponibilizar na sua loja. - > Consulte na sua conta do PagSeguro os meios de pagamento que estão habilitados. - - - *PagSeguro (Padrão ou Lightbox)* - - **ativar**: ativa/desativa o meio de pagamento PagSeguro (padrão ou lightbox). - - **checkout**: especifica o modelo de checkout que será utilizado. É possível escolher entre checkout padrão ou checkout lightbox. - - **nome de exibição**: define o nome que será utilizado para o meio de pagamento na tela de checkout. - - **posição na tela de checkout (Sort Order)**: Configura a ordem de exibição deste meio de pagamento na sua loja. Esta ordem é relativa à todos os outros meios de pagamento configurados na sua loja. - - - *Checkout Transparente - Cartão de Crédito* - - **ativar**: ativa/desativa o meio de pagamento Checkout Transparente - Cartão de Crédito. - - **nome de exibição**: define o nome que será utilizado para esse meio de pagamento na tela de checkout. - - **posição na tela de checkout (Sort Order)**: Configura a ordem de exibição deste meio de pagamento na sua loja. Esta ordem é relativa à todos os outros meios de pagamento configurados na sua loja. - - - - *Checkout Transparente - Boleto Bancário* - - **ativar**: ativa/desativa o meio de pagamento Checkout Transparente - Boleto Bancário. - - **nome de exibição**: define o nome que será utilizado para esse meio de pagamento na tela de checkout. - - **posição na tela de checkout (Sort Order)**: Configura a ordem de exibição deste meio de pagamento na sua loja. Esta ordem é relativa à todos os outros meios de pagamento configurados na sua loja. - - - - *Checkout Transparente - Débito Online* - - **ativar**: ativa/desativa o meio de pagamento Checkout Transparente - Débito Online. - - **nome de exibição**: define o nome que será utilizado para esse meio de pagamento na tela de checkout. - - **posição na tela de checkout (Sort Order)**: Configura a ordem de exibição deste meio de pagamento na sua loja. Esta ordem é relativa à todos os outros meios de pagamento configurados na sua loja. - - - Transações ------------- --- - Para realizar consultas e outras operações acesse o menu PagSeguro -> *Transação*, onde *Transação* pode ser escolhida as opções: Conciliação, Abandonadas, Cancelamento, Estorno. As opções disponíveis estão descritas abaixo: - - - **abandonadas**: permite pesquisar as transações que foram abandonadas dentro da quantidade de dias definidos para a pesquisa. - - **cancelamento**: esta pesquisa retornará todas as transações que estejam com status "em análise" e "aguardando pagamento", dentro da quantidade de dias definidos para a pesquisa. Desta forma você pode solicitar o cancelamento destas transações. - - **conciliação**: permite consultar as transações efetivadas no PagSeguro nos últimos 30 dias. A pesquisa retornará um comparativo com o status das transações em sua base local e o status atual da transação no PagSeguro, desta forma você pode identificar e atualizar transações com status divergentes. - - **estorno**: esta pesquisa retornará todas as transações que estejam com status "paga", "disponível" e "em disputa", dentro da quantidade de dias definidos para a pesquisa. Desta forma você pode solicitar o estorno dos valores pagos para seus compradores. - > É aconselhável que antes de usar as funcionalidades de **estorno** ou **cancelamento** você faça a **conciliação** de suas transações para obter os status mais atuais. +**Configurações Gerais** + +- **ambiente**: especifica em que ambiente as transações serão feitas _(produção/sandbox)_. +- **e-mail**: e-mail cadastrado no PagSeguro. +- **token**: token cadastrado no PagSeguro. +- **url de redirecionamento**: ao final do fluxo de pagamento no PagSeguro, seu cliente será redirecionado automaticamente para a página de confirmação em sua loja ou então para a URL que você informar neste campo. Para ativar o redirecionamento ao final do pagamento é preciso ativar o serviço de [Pagamentos via API]. Obs.: Esta URL é informada automaticamente e você só deve alterá-la caso deseje que seus clientes sejam redirecionados para outro local. +- **url de notificação**: sempre que uma transação mudar de status, o PagSeguro envia uma notificação para sua loja. **O valor padrão que deve ser utilizado pelo módulo é: http://www.minhaloja.com.br/index.php/pagseguro/notification/response** + - _Observação: Esta URL só deve ser alterada caso você deseje receber as notificações em outro local._ +- **charset**: codificação do seu sistema (ISO-8859-1 ou UTF-8). +- **ativar log**: ativa/desativa a geração de logs. +- **diretório**: informe o local e nome do arquivo a partir da raíz de instalação do Magento onde se deseja criar o arquivo de log. Ex.: var/log/pagseguro.log. + - _Por padrão o módulo virá configurado para salvar o arquivo de log em var/log/pagseguro.log_. +- **listar transações abandonadas?**: ativa/desativa a pesquisa de transações que foram abandonadas no checkout do PagSeguro. +- **transações -> abandonadas**: permite consultar as transações que foram abandonadas nos últimos 10 dias, desta forma você pode enviar emails de recuperação de venda. O e-mail conterá um link que redirecionará o comprador para o fluxo de pagamento, exatamente no ponto onde ele parou. +- **habilitar recuperação de carrinho**: Habilita a recuperação de carrinho do PagSeguro. (por padrão está desabilitada) +- **listar parcelamento**: Habilita a exibição de uma listagem de parcelas na tela de visualização do produto. (Irá exibir o maior parcelamento disponível para o produto na tela de exibição do mesmo) + +--- + +**Configurar Tipos de Checkout** +Nesta seção você irá configurar os meios de pagamento do PagSeguro que deseja disponibilizar na sua loja. + +> Consulte na sua conta do PagSeguro os meios de pagamento que estão habilitados. + +- _PagSeguro (Padrão ou Lightbox)_ + + - **ativar**: ativa/desativa o meio de pagamento PagSeguro (padrão ou lightbox). + - **checkout**: especifica o modelo de checkout que será utilizado. É possível escolher entre checkout padrão ou checkout lightbox. + - **nome de exibição**: define o nome que será utilizado para o meio de pagamento na tela de checkout. + - **posição na tela de checkout (Sort Order)**: Configura a ordem de exibição deste meio de pagamento na sua loja. Esta ordem é relativa à todos os outros meios de pagamento configurados na sua loja. + - **oferecer desconto para ...**: ativa/desativa desconto para checkouts por meio de pagamento (cartão de crédito, boleto, débito online, depósito em conta e saldo pagseguro) + - **percentual de desconto**: define o percentual de desconto a ser concedido para o meio de pagamento escolhido (Aceita valores de 0.01 à 99.99) + +- _Checkout Transparente - Cartão de Crédito_ + + - **ativar**: ativa/desativa o meio de pagamento Checkout Transparente - Cartão de Crédito. + - **nome de exibição**: define o nome que será utilizado para esse meio de pagamento na tela de checkout. + - **posição na tela de checkout (Sort Order)**: Configura a ordem de exibição deste meio de pagamento na sua loja. Esta ordem é relativa à todos os outros meios de pagamento configurados na sua loja. + +- _Checkout Transparente - Boleto Bancário_ + + - **ativar**: ativa/desativa o meio de pagamento Checkout Transparente - Boleto Bancário. + - **nome de exibição**: define o nome que será utilizado para esse meio de pagamento na tela de checkout. + - **posição na tela de checkout (Sort Order)**: Configura a ordem de exibição deste meio de pagamento na sua loja. Esta ordem é relativa à todos os outros meios de pagamento configurados na sua loja. + +- _Checkout Transparente - Débito Online_ + - **ativar**: ativa/desativa o meio de pagamento Checkout Transparente - Débito Online. + - **nome de exibição**: define o nome que será utilizado para esse meio de pagamento na tela de checkout. + - **posição na tela de checkout (Sort Order)**: Configura a ordem de exibição deste meio de pagamento na sua loja. Esta ordem é relativa à todos os outros meios de pagamento configurados na sua loja. + +## Transações + +--- + +Para realizar consultas e outras operações acesse o menu PagSeguro -> _Transação_, onde _Transação_ pode ser escolhida as opções: Conciliação, Abandonadas, Cancelamento, Estorno. As opções disponíveis estão descritas abaixo: + +- **abandonadas**: permite pesquisar as transações que foram abandonadas dentro da quantidade de dias definidos para a pesquisa. +- **cancelamento**: esta pesquisa retornará todas as transações que estejam com status "em análise" e "aguardando pagamento", dentro da quantidade de dias definidos para a pesquisa. Desta forma você pode solicitar o cancelamento destas transações. +- **conciliação**: permite consultar as transações efetivadas no PagSeguro nos últimos 30 dias. A pesquisa retornará um comparativo com o status das transações em sua base local e o status atual da transação no PagSeguro, desta forma você pode identificar e atualizar transações com status divergentes. +- **estorno**: esta pesquisa retornará todas as transações que estejam com status "paga", "disponível" e "em disputa", dentro da quantidade de dias definidos para a pesquisa. Desta forma você pode solicitar o estorno dos valores pagos para seus compradores. + +> É aconselhável que antes de usar as funcionalidades de **estorno** ou **cancelamento** você faça a **conciliação** de suas transações para obter os status mais atuais. + +## Inputs -Inputs ---------- --- -| Dados do comprador |Tipo | Esperado | -| ---------------------------|:----:|:------------------------------------------------------------------------------:| -| Email | {Pattern - ^([a-zA-Z0-9_])+([@])+([a-zA-Z0-9_])+([.])+([a-zA-Z0-9_])}| email@email.em | -| Name / Nome | {String} | Nome | -| Last Name / Sobrenome | {String} | Sobrenome | -| Company / Empresa | {String} | Empresa | -| Address / Endereço | {String, Integer} |Endereço, Numero| -| Address 2 / Bairro /Endereço (Linha 2) | {String} | Bairro | -| PostCode / CEP | {Integer or String} | 99999999 / 99999-999 | -| City / Cidade | {String} | Cidade | -| Country / País | {String} | País | -| State or Province / Estado | {String} | Estado | -| Aditional information / Informações adicionais | {String} |Complemento | -| Phone / Telefone residencial | {Integer} - {DDD+NUMBER} | 99999999999 | -| Cell Phone / Telefone celular | {Integer} - {DDD+NUMBER} | 99999999999 | - - -Dúvidas? ----------- + +| Dados do comprador | Tipo | Esperado | +| --------------------- | :-------------------------------------------------------------------: | :------------: | +| Email | {Pattern - ^([a-zA-Z0-9_])+([@])+([a-zA-Z0-9_])+([.])+([a-zA-Z0-9_])} | email@email.em | +| Name / Nome | {String} | Nome | +| Last Name / Sobrenome | {String} | Sobrenome | +| Company / Empresa | {String} | Empresa | + +| Configuração de endereço de 4 linhas: +| Address 1 / Endereço 1 / Rua | {String} |Endereço (rua)| +| Address 2 / Endereço 2 / Número | {Integer} |Número | +| Address 3 / Endereço 3 / Complemento | {String} |Complemento | +| Address 4 / Endereço 4 / Bairro | {String} |Bairro | +| Configuração de endereço padrão Magento 2 (2 linhas): +| Address / Endereço | {String, Integer} |Endereço, Numero| +| Address 2 / Bairro /Endereço (Linha 2) | {String} | Bairro | +| PostCode / CEP | {Integer or String} | 99999999 / 99999-999 | +| City / Cidade | {String} | Cidade | +| Country / País | {String} | País | +| State or Province / Estado | {String} | Estado | +| Aditional information / Informações adicionais | {String} |Complemento | +| Phone / Telefone residencial | {Integer} - {DDD+NUMBER} | 99999999999 | +| Cell Phone / Telefone celular | {Integer} - {DDD+NUMBER} | 99999999999 | + +## Dúvidas? + --- + Caso tenha dúvidas ou precise de suporte, acesse nosso [fórum]. +## Changelog + +Para consultar o log de alterações acesse o arquivo [CHANGELOG.md](CHANGELOG.md). + +## Licença -Changelog ---------- -1.4.0 -- Alterado o fluxo do checkout transparente (na própria tela de checkout do Magento) -- Alterada a forma de configurar o módulo e os meios de pagamento do PagSeguro, que agora são configurados individualmente. -- Melhorias gerais e correções de bugs: transações do admin, css muito abrangente, remoção de arquivos velhos e desnecessários, refatorações. - -1.3.0 -- Adicionada validação e mensagens de erro (frontend) nos formulários do checkout transparente - -1.2.6 -- Melhoria na configuração do log na interface administrativa -- Adicionada seção de atualização do módulo e atualização geral da documentação (README.md) -- Correção de bugs quando o pedido deixava de existir ou a sessão era encerrada -- Correçao para aceitar CVV de 4 digitos -- Melhoria no acesso aos dados do endereço do cliente - -1.2.1 -- Alterada a biblioteca JavaScript utilizada nas máscaras. - -1.2.0 -- Adicionada opção para utilizar o Checkout Transparente. - -1.1.0 -- Possibilidade de consultar e solicitar o cancelamento de transações; -- Possibilidade de consultar e solicitar o estorno de transações; -- Possibilidade de definir descontos com base no meio de pagamento escolhido durante o checkout PagSeguro; - -1.0.0 -- Adicionando opção para utilização do Checkout Lightbox. -- Integração com API de Notificação. -- Integração com API de Pagamento do PagSeguro. -- Configuração do Setup do módulo. -- Adicionado meio de pagamento ao Magento2 -- Versão inicial. - -Licença -------- --- + Copyright 2016 PagSeguro Internet LTDA. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -196,34 +185,34 @@ http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +## Notas -Notas ---------- --- - - O PagSeguro somente aceita pagamento utilizando a moeda Real brasileiro (BRL). - - Certifique-se que o email e o token informados estejam relacionados a uma conta que possua o perfil de vendedor ou empresarial. - - Certifique-se que tenha definido corretamente o charset de acordo com a codificação (ISO-8859-1 ou UTF-8) do seu sistema. Isso irá prevenir que as transações gerem possíveis erros ou quebras ou ainda que caracteres especiais possam ser apresentados de maneira diferente do habitual. - - Para que ocorra normalmente a geração de logs, certifique-se que o diretório e o arquivo de log tenham permissões de leitura e escrita. -Contribuições -------------- +- O PagSeguro somente aceita pagamento utilizando a moeda Real brasileiro (BRL). +- Certifique-se que o email e o token informados estejam relacionados a uma conta que possua o perfil de vendedor ou empresarial. +- Certifique-se que tenha definido corretamente o charset de acordo com a codificação (ISO-8859-1 ou UTF-8) do seu sistema. Isso irá prevenir que as transações gerem possíveis erros ou quebras ou ainda que caracteres especiais possam ser apresentados de maneira diferente do habitual. +- Para que ocorra normalmente a geração de logs, certifique-se que o diretório e o arquivo de log tenham permissões de leitura e escrita. + +## Contribuições + --- -Achou e corrigiu um bug ou tem alguma feature em mente e deseja contribuir? -* Faça um fork. -* Adicione sua feature ou correção de bug. -* Envie um pull request no [GitHub]. -* Obs.: O Pull Request não deve ser enviado para o branch master e sim para o branch correspondente a versão ou para a branch de desenvolvimento. +Achou e corrigiu um bug ou tem alguma feature em mente e deseja contribuir? +- Faça um fork. +- Adicione sua feature ou correção de bug. +- Envie um pull request no [GitHub]. +- Obs.: O Pull Request não deve ser enviado para o branch master e sim para o branch correspondente a versão ou para a branch de desenvolvimento. - [API de Pagamentos]: https://dev.pagseguro.uol.com.br/documentacao/pagamentos - [API de Notificações]: https://pagseguro.uol.com.br/v2/guia-de-integracao/api-de-notificacoes.html + [api de pagamentos]: https://dev.pagseguro.uol.com.br/documentacao/pagamentos + [api de notificações]: https://pagseguro.uol.com.br/v2/guia-de-integracao/api-de-notificacoes.html [fórum]: https://comunidade.pagseguro.uol.com.br/hc/pt-br/community/topics - [Pagamentos via API]: https://pagseguro.uol.com.br/integracao/pagamentos-via-api.jhtml - [Notificação de Transações]: https://pagseguro.uol.com.br/integracao/notificacao-de-transacoes.jhtml - [Magento]: https://www.magentocommerce.com/ - [PHP]: http://www.php.net/ - [SPL]: http://php.net/manual/en/book.spl.php - [cURL]: http://php.net/manual/en/book.curl.php - [DOM]: http://php.net/manual/en/book.dom.php - [GitHub]: https://github.com/pagseguro/magento2 + [pagamentos via api]: https://pagseguro.uol.com.br/integracao/pagamentos-via-api.jhtml + [notificação de transações]: https://pagseguro.uol.com.br/integracao/notificacao-de-transacoes.jhtml + [magento]: https://www.magentocommerce.com/ + [php]: http://www.php.net/ + [spl]: http://php.net/manual/en/book.spl.php + [curl]: http://php.net/manual/en/book.curl.php + [dom]: http://php.net/manual/en/book.dom.php + [github]: https://github.com/pagseguro/magento2 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..034e848 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 5.1.x | :white_check_mark: | +| 5.0.x | :x: | +| 4.0.x | :white_check_mark: | +| < 4.0 | :x: | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. diff --git a/Setup/UpgradeSchema.php b/Setup/UpgradeSchema.php index 0157b17..37bd3c3 100644 --- a/Setup/UpgradeSchema.php +++ b/Setup/UpgradeSchema.php @@ -49,6 +49,10 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con $this->integratePagSeguroAndOrdersGrid($setup); $this->cleanUiBookmark($setup); } + + if (version_compare($context->getVersion(), '2.0.2') < 0) { + $this->addPartiallyRefundedColumnPagseguroOrders($setup); + } $setup->endSetup(); @@ -65,7 +69,7 @@ private function createPagSeguroOrdersTable($setup) // Get pagseguro orders table $tableName = $setup->getTable(self::PAGSEGURO_ORDERS); // Check if the table already exists - if ($setup->getConnection()->isTableExists($tableName) != true) { + if ($setup->getConnection()->isTableExists($tableName) !== true) { // Create pagseguro orders table $table = $setup->getConnection() ->newTable($tableName) @@ -174,4 +178,25 @@ private function cleanUiBookmark($setup) $setup->getConnection() ->delete($setup->getTable('ui_bookmark'), "namespace='sales_order_grid'"); } + + private function addPartiallyRefundedColumnPagSeguroOrders($setup) + { + // Get pagseguro orders table + $tableName = $setup->getTable(self::PAGSEGURO_ORDERS); + + // Check if the table already exists + if ($setup->getConnection()->isTableExists($tableName)=== true && $setup->getConnection()->tableColumnExists($tableName, 'partially_refunded') === false) { + $setup->getConnection() + ->addColumn( + $tableName, + 'partially_refunded', + array( + 'type' => Table::TYPE_BOOLEAN, + 'nullable' => false, + 'default' => 0, + 'comment' => 'Show if order is already partially refunded', + ) + ); + } + } } diff --git a/composer.json b/composer.json index 98dca87..ae999f5 100644 --- a/composer.json +++ b/composer.json @@ -1,20 +1,20 @@ { - "name": "pagseguro/magento2", - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], - "require": { - "php": "~5.5.0|~5.6.0|~7.0.0", - "pagseguro/pagseguro-php-sdk": "3.*" - }, - "autoload": { - "files": [ - "registration.php" - ], - "psr-4": { - "UOL\\PagSeguro\\": "" - } - } + "name": "pagseguro/magento2", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "require": { + "php": "~7.3||~7.4", + "pagseguro/pagseguro-php-sdk": "4.*" + }, + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "UOL\\PagSeguro\\": "" + } + } } diff --git a/etc/acl.xml b/etc/acl.xml index b423ff9..4ae6c54 100755 --- a/etc/acl.xml +++ b/etc/acl.xml @@ -1,13 +1,15 @@ - - - - - - - + + + + + + + + + - + diff --git a/etc/adminhtml/menu.xml b/etc/adminhtml/menu.xml index 33a3d88..729a3ba 100755 --- a/etc/adminhtml/menu.xml +++ b/etc/adminhtml/menu.xml @@ -30,5 +30,6 @@ + diff --git a/etc/adminhtml/system/general_configuration.xml b/etc/adminhtml/system/general_configuration.xml index c172cfa..33110e5 100644 --- a/etc/adminhtml/system/general_configuration.xml +++ b/etc/adminhtml/system/general_configuration.xml @@ -61,6 +61,13 @@ UOL\PagSeguro\Model\System\Config\Yesno payment/pagseguro/abandoned_active + + + Habilita/desabilita a recuperação de carrinho do PagSeguro. + Para saber mais sobre a recuperação de carrinho do PagSeguro, clique <a href="https://pagseguro.uol.com.br/para-seu-negocio/online/recuperacao-de-carrinho" target="_blank">aqui</a>. + UOL\PagSeguro\Model\System\Config\Yesno + payment/pagseguro/shopping_cart_recovery + Ativar a exibição da listagem de parcelas na tela de visualização do produto. (Irá exibir o maior parcelamento disponível para o produto no pagamento com cartão de crédito) diff --git a/etc/adminhtml/system/payment_boleto.xml b/etc/adminhtml/system/payment_boleto.xml index 04b1590..87ee670 100644 --- a/etc/adminhtml/system/payment_boleto.xml +++ b/etc/adminhtml/system/payment_boleto.xml @@ -17,7 +17,7 @@ required-entry - + Irá aparecer depois de todos os meios de pagamento configurados na sua loja com valor menor e antes dos de valor maior. payment/pagseguro_boleto/sort_order validate-zero-or-greater diff --git a/etc/adminhtml/system/payment_credit_card.xml b/etc/adminhtml/system/payment_credit_card.xml index 086b182..fab1b25 100644 --- a/etc/adminhtml/system/payment_credit_card.xml +++ b/etc/adminhtml/system/payment_credit_card.xml @@ -17,7 +17,7 @@ required-entry - + Irá aparecer depois de todos os meios de pagamento configurados na sua loja com valor menor e antes dos de valor maior. payment/pagseguro_credit_card/sort_order validate-zero-or-greater diff --git a/etc/adminhtml/system/payment_default_lightbox.xml b/etc/adminhtml/system/payment_default_lightbox.xml index 79fcb3c..ba6e1ef 100644 --- a/etc/adminhtml/system/payment_default_lightbox.xml +++ b/etc/adminhtml/system/payment_default_lightbox.xml @@ -26,7 +26,7 @@ required-entry - + Irá aparecer depois de todos os meios de pagamento configurados na sua loja com valor menor e antes dos de valor maior. payment/pagseguro_default_lightbox/sort_order validate-zero-or-greater @@ -35,5 +35,86 @@ 1 + + + + Ao ativar esta opção, seu comprador vai receber um desconto caso escolha pagar com este meio de pagamento. O desconto será aplicado com base no subtotal do checkout PagSeguro. + UOL\PagSeguro\Model\System\Config\Yesno + payment/pagseguro_default_lightbox/discount_credit_card + + + + + 1 + + required-entry validate-number validate-number-range number-range-0.01-99.99 + Informe o percentual de desconto a ser concedido para este meio de pagamento. Aceita valores de 0.01 à 99.99 + payment/pagseguro_default_lightbox/discount_credit_card_value + + + + + Ao ativar esta opção, seu comprador vai receber um desconto caso escolha pagar com este meio de pagamento. O desconto será aplicado com base no subtotal do checkout PagSeguro. + UOL\PagSeguro\Model\System\Config\Yesno + payment/pagseguro_default_lightbox/discount_boleto + + + + + 1 + + required-entry validate-number validate-number-range number-range-0.01-99.99 + Informe o percentual de desconto a ser concedido para este meio de pagamento. Aceita valores de 0.01 à 99.99 + payment/pagseguro_default_lightbox/discount_boleto_value + + + + + Ao ativar esta opção, seu comprador vai receber um desconto caso escolha pagar com este meio de pagamento. O desconto será aplicado com base no subtotal do checkout PagSeguro. + UOL\PagSeguro\Model\System\Config\Yesno + payment/pagseguro_default_lightbox/discount_online_debit + + + + + 1 + + required-entry validate-number validate-number-range number-range-0.01-99.99 + Informe o percentual de desconto a ser concedido para este meio de pagamento. Aceita valores de 0.01 à 99.99 + payment/pagseguro_default_lightbox/discount_online_debit_value + + + + + Ao ativar esta opção, seu comprador vai receber um desconto caso escolha pagar com este meio de pagamento. O desconto será aplicado com base no subtotal do checkout PagSeguro. + UOL\PagSeguro\Model\System\Config\Yesno + payment/pagseguro_default_lightbox/discount_deposit_account + + + + + 1 + + required-entry validate-number validate-number-range number-range-0.01-99.99 + Informe o percentual de desconto a ser concedido para este meio de pagamento. Aceita valores de 0.01 à 99.99 + payment/pagseguro_default_lightbox/discount_deposit_account_value + + + + + Ao ativar esta opção, seu comprador vai receber um desconto caso escolha pagar com este meio de pagamento. O desconto será aplicado com base no subtotal do checkout PagSeguro. + UOL\PagSeguro\Model\System\Config\Yesno + payment/pagseguro_default_lightbox/discount_balance + + + + + 1 + + required-entry validate-number validate-number-range number-range-0.01-99.99 + Informe o percentual de desconto a ser concedido para este meio de pagamento. Aceita valores de 0.01 à 99.99 + payment/pagseguro_default_lightbox/discount_balance_value + + diff --git a/etc/adminhtml/system/payment_online_debit.xml b/etc/adminhtml/system/payment_online_debit.xml index 71c3f3c..bbd3e67 100644 --- a/etc/adminhtml/system/payment_online_debit.xml +++ b/etc/adminhtml/system/payment_online_debit.xml @@ -17,7 +17,7 @@ required-entry - + Irá aparecer depois de todos os meios de pagamento configurados na sua loja com valor menor e antes dos de valor maior. payment/pagseguro_online_debit/sort_order validate-zero-or-greater diff --git a/etc/module.xml b/etc/module.xml index 0c3f983..25a654e 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -23,7 +23,7 @@ */ --> 
 - + diff --git a/view/adminhtml/layout/pagseguro_transactions_index.xml b/view/adminhtml/layout/pagseguro_transactions_index.xml new file mode 100755 index 0000000..5c33a3a --- /dev/null +++ b/view/adminhtml/layout/pagseguro_transactions_index.xml @@ -0,0 +1,27 @@ + + + + PagSeguro + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/view/adminhtml/requirejs-config.js b/view/adminhtml/requirejs-config.js index 12f31a8..d63e065 100755 --- a/view/adminhtml/requirejs-config.js +++ b/view/adminhtml/requirejs-config.js @@ -4,10 +4,10 @@ */ var config = { - map: { - '*': { - "datatables": "UOL_PagSeguro/js/jquery.dataTables.min", - "public": "UOL_PagSeguro/js/public", - } - } -}; \ No newline at end of file + map: { + '*': { + datatables: 'UOL_PagSeguro/js/jquery.dataTables.min', + public: 'UOL_PagSeguro/js/public', + }, + }, +}; diff --git a/view/adminhtml/templates/abandoned/content.phtml b/view/adminhtml/templates/abandoned/content.phtml index 928f291..437cc6a 100755 --- a/view/adminhtml/templates/abandoned/content.phtml +++ b/view/adminhtml/templates/abandoned/content.phtml @@ -79,10 +79,10 @@ // Check/Uncheck all checkboxes. $('#abandoned-mass-select-checkbox').on('click', function () { - if (this.checked == true) { + if (this.checked=== true) { $('[data-target="abandoned"]').prop('checked', true); } - if (this.checked == false) { + if (this.checked=== false) { $('[data-target="abandoned"]').prop('checked', false); } }) @@ -95,7 +95,7 @@ var hasOne = false; jQuery.each($('[data-target="abandoned"]'), function(index, value) { if ( $(value).is(':checked') ) { - if (hasOne != true) hasOne = true; + if (hasOne !== true) hasOne = true; } }); if (hasOne === false) { diff --git a/view/adminhtml/templates/abandoned/toolbar/buttons.phtml b/view/adminhtml/templates/abandoned/toolbar/buttons.phtml index 4198e96..e5bfbf9 100755 --- a/view/adminhtml/templates/abandoned/toolbar/buttons.phtml +++ b/view/adminhtml/templates/abandoned/toolbar/buttons.phtml @@ -2,7 +2,7 @@
+ +
+ \ No newline at end of file diff --git a/view/adminhtml/templates/refund/header.phtml b/view/adminhtml/templates/refund/header.phtml index 4ad1a1d..1047302 100755 --- a/view/adminhtml/templates/refund/header.phtml +++ b/view/adminhtml/templates/refund/header.phtml @@ -1,3 +1,8 @@

Com esta funcionalidade você poderá estornar os valores de transações que estejam nos status “Paga”, “Disponível” e “Em disputa”. É aconselhável que antes de usar esta funcionalidade você faça a conciliação de suas transações para obter os status mais atuais.

+

+ Atenção: Ao realizar um Estorno total a transação será estornada e terá seu status alterado no PagSeguro para + "Devolvida". Ao realizar um Estorno parcial será estornado o valor inserido, contudo, o status da transação no PagSeguro não será + alterado (a transação manterá o seu último status). +

\ No newline at end of file diff --git a/view/adminhtml/templates/transactions/content.phtml b/view/adminhtml/templates/transactions/content.phtml new file mode 100644 index 0000000..c16b8b0 --- /dev/null +++ b/view/adminhtml/templates/transactions/content.phtml @@ -0,0 +1,195 @@ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
DataID MagentoID PagSeguroAmbienteStatus PagSeguroAção
+
+ +
+ + + + + + + +
+ + + +
+
+ + \ No newline at end of file diff --git a/view/adminhtml/templates/transactions/header.phtml b/view/adminhtml/templates/transactions/header.phtml new file mode 100755 index 0000000..9aace9f --- /dev/null +++ b/view/adminhtml/templates/transactions/header.phtml @@ -0,0 +1,4 @@ +
+

Com esta funcionalidade você poderá listar as suas transações. Caso não encontre uma transação ou se o status de uma transação estiver desatualizado é aconselhável que você use a conciliação de transações.

+

Atenção: Toda transação estornada de forma parcial não tem seu status alterado. Por isso, ao filtrar por "Estornada Parcialmente" o pedido exibirá o seu status original.

+
\ No newline at end of file diff --git a/view/adminhtml/templates/transactions/toolbar/buttons.phtml b/view/adminhtml/templates/transactions/toolbar/buttons.phtml new file mode 100755 index 0000000..e69de29 diff --git a/view/adminhtml/web/css/styles.css b/view/adminhtml/web/css/styles.css old mode 100755 new mode 100644 index 89b18e1..791b324 --- a/view/adminhtml/web/css/styles.css +++ b/view/adminhtml/web/css/styles.css @@ -1,23 +1,211 @@ .data-grid td { - text-align: center!important; + text-align: center !important; +} + +.data-grid .data-grid-th input[type='date'] { + width: 138px; } .page-wrapper { - background: url(../images/background.png) no-repeat; - -moz-background-size: 100% 40%; - -webkit-background-size: 100% 40%; - background-size: 100% 40%; + background: url(../images/background.png) no-repeat; + -moz-background-size: 100% 40%; + -webkit-background-size: 100% 40%; + background-size: 100% 40%; } -#conciliation-search, #abandoned-search, #cancellation-search, #refund-search { - background: #77B800!important; - border: 1px #639900 solid; +#conciliation-search, +#abandoned-search, +#cancellation-search, +#refund-search, +#transactions-filter, +#partial-refund { + background: #77b800 !important; + border: 1px #639900 solid; } .abandoned-error { - height: 100px; - text-align: center; - padding: 40px; - margin-bottom: 20px; + height: 100px; + text-align: center; + padding: 40px; + margin-bottom: 20px; +} + +.field-error { + border-color: #e74c3c !important; +} + +.modal-overlay { + z-index: 899; + display: none; + background: rgba(0, 0, 0, 0.35); + bottom: 0; + left: 0; + position: fixed; + right: 0; + top: 0; +} + +.hidden-groups { + display: none; +} + +#pagseguro-datatable { + width: 100% !important; +} + +/*#pagseguro-datatable thead tr th:first-child { + width: 120px; +} + +#pagseguro-datatable thead tr th:nth-child(5) { + width: 230px; +}*/ + +#pagseguro-datatable thead tr th:first-child input:first-child { + margin-bottom: 5px; +} + +#pagseguro-datatable thead tr:nth-child(2) th input { + width: 97%; +} + +#pagseguro-datatable thead tr:nth-child(2) th:nth-child(1) input { + max-width: 120px; +} + +#pagseguro-datatable thead tr:nth-child(2) th:nth-child(2) input { + max-width: 110px; +} + +#pagseguro-datatable thead tr:nth-child(2) th:nth-child(3) input { + max-width: 320px; +} + +#pagseguro-datatable thead tr:nth-child(2) th:nth-child(6) input { + max-width: 70px; +} + +#pagseguro-datatable thead tr:nth-child(2) th { + text-align: center; +} + +#modal-partial-refund .modal-inner-wrap { + width: 325px; +} + +#modal-partial-refund .modal-inner-wrap .modal-content h2 { + margin-bottom: 20px; + padding-top: 5px; +} + +#modal-partial-refund .modal-inner-wrap .modal-content h5 { + font-weight: bold; +} + +#modal-partial-refund .modal-inner-wrap .modal-content form { + margin-bottom: 20px; +} + +#modal-partial-refund .modal-inner-wrap .modal-content form p.error { + color: #e74c3c; + margin-left: 22px; +} + +.link { + color: #025ec7; + cursor: pointer; + outline: medium none; + text-decoration: none; +} + +button.align-right { + float: right; + margin-bottom: 20px; + margin-right: 15px; +} + +/*List Details*/ +.list-datails .group { + border-bottom: solid 1px #dedede; + margin: 0 40px; + padding: 10px 0; + overflow: hidden; + font-size: 13px; +} + +.list-datails #payment-group { + margin-bottom: 15px; +} + +.list-datails .group > div:not(:last-child) { + display: flex; + justify-content: flex-start; +} + +.list-datails .group h4 { + margin-bottom: 0; +} + +.list-datails .group > div > dl:not(:last-child) { + margin-right: 40px; +} + +.list-datails .group div dl:first-child { + width: 112px; +} + +.list-datails .table { + background-color: #e9e9e9; + padding: 10px 25px; + margin-top: 5px; + max-height: 110px; + overflow: auto; + margin-bottom: 8px; +} + +.list-datails .group-title { + font-weight: 700; + display: flex; + flex-direction: row; + justify-content: flex-start; + margin-top: 7px; +} + +.list-datails .group-title > div:not(:first-child), +.itens-line > div:not(:first-child) { + margin-right: 50px; +} + +.list-datails .itens-cell { + width: 100px; +} + +.list-datails .description-cell { + width: 200px; + margin-right: 0 !important; } +.list-datails .itens-line { + display: flex; + flex-flow: row wrap; + justify-content: flex-start; +} + +.list-datails .group .table .rate { + display: flex; + justify-content: flex-start; +} + +.list-datails .group .table .rate dl { + width: 140px; + margin-top: 7px; + margin-right: 45px; +} + +.list-datails .hidden-groups { + display: none; +} + +.list-datails dl { + margin-bottom: 10px; +} diff --git a/view/adminhtml/web/js/jquery.dataTables.min.js b/view/adminhtml/web/js/jquery.dataTables.min.js index 5cb6235..9082ff8 100755 --- a/view/adminhtml/web/js/jquery.dataTables.min.js +++ b/view/adminhtml/web/js/jquery.dataTables.min.js @@ -24,15255 +24,14865 @@ /*jslint evil: true, undef: true, browser: true */ /*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ -(function( factory ) { - "use strict"; - - if ( typeof define === 'function' && define.amd ) { - // AMD - define( ['jquery'], function ( $ ) { - return factory( $, window, document ); - } ); - } - else if ( typeof exports === 'object' ) { - // CommonJS - module.exports = function (root, $) { - if ( ! root ) { - // CommonJS environments without a window global must pass a - // root. This will give an error otherwise - root = window; - } - - if ( ! $ ) { - $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window - require('jquery') : - require('jquery')( root ); - } - - return factory( $, root, root.document ); - }; - } - else { - // Browser - factory( jQuery, window, document ); - } -} -(function( $, window, document, undefined ) { - "use strict"; +(function (factory) { + 'use strict'; + + if (typeof define === 'function' && define.amd) { + // AMD + define(['jquery'], function ($) { + return factory($, window, document); + }); + } else if (typeof exports === 'object') { + // CommonJS + module.exports = function (root, $) { + if (!root) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if (!$) { + $ = + typeof window !== 'undefined' // jQuery's factory checks for a global window + ? require('jquery') + : require('jquery')(root); + } + + return factory($, root, root.document); + }; + } else { + // Browser + factory(jQuery, window, document); + } +})(function ($, window, document, undefined) { + 'use strict'; + + /** + * DataTables is a plug-in for the jQuery Javascript library. It is a highly + * flexible tool, based upon the foundations of progressive enhancement, + * which will add advanced interaction controls to any HTML table. For a + * full list of features please refer to + * [DataTables.net](href="http://datatables.net). + * + * Note that the `DataTable` object is not a global variable but is aliased + * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may + * be accessed. + * + * @class + * @param {object} [init={}] Configuration object for DataTables. Options + * are defined by {@link DataTable.defaults} + * @requires jQuery 1.7+ + * + * @example + * // Basic initialisation + * $(document).ready( function { + * $('#example').dataTable(); + * } ); + * + * @example + * // Initialisation with configuration options - in this case, disable + * // pagination and sorting. + * $(document).ready( function { + * $('#example').dataTable( { + * "paginate": false, + * "sort": false + * } ); + * } ); + */ + var DataTable = function (options) { + /** + * Perform a jQuery selector action on the table's TR elements (from the tbody) and + * return the resulting jQuery object. + * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on + * @param {object} [oOpts] Optional parameters for modifying the rows to be included + * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter + * criterion ("applied") or all TR elements (i.e. no filter). + * @param {string} [oOpts.order=current] Order of the TR elements in the processed array. + * Can be either 'current', whereby the current sorting of the table is used, or + * 'original' whereby the original order the data was read into the table is used. + * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page + * ("current") or not ("all"). If 'current' is given, then order is assumed to be + * 'current' and filter is 'applied', regardless of what they might be given as. + * @returns {object} jQuery object, filtered by the given selector. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Highlight every second row + * oTable.$('tr:odd').css('backgroundColor', 'blue'); + * } ); + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Filter to rows with 'Webkit' in them, add a background colour and then + * // remove the filter, thus highlighting the 'Webkit' rows only. + * oTable.fnFilter('Webkit'); + * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue'); + * oTable.fnFilter(''); + * } ); + */ + this.$ = function (sSelector, oOpts) { + return this.api(true).$(sSelector, oOpts); + }; /** - * DataTables is a plug-in for the jQuery Javascript library. It is a highly - * flexible tool, based upon the foundations of progressive enhancement, - * which will add advanced interaction controls to any HTML table. For a - * full list of features please refer to - * [DataTables.net](href="http://datatables.net). + * Almost identical to $ in operation, but in this case returns the data for the matched + * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes + * rather than any descendants, so the data can be obtained for the row/cell. If matching + * rows are found, the data returned is the original data array/object that was used to + * create the row (or a generated array if from a DOM source). + * + * This method is often useful in-combination with $ where both functions are given the + * same parameters and the array indexes will match identically. + * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on + * @param {object} [oOpts] Optional parameters for modifying the rows to be included + * @param {string} [oOpts.filter=none] Select elements that meet the current filter + * criterion ("applied") or all elements (i.e. no filter). + * @param {string} [oOpts.order=current] Order of the data in the processed array. + * Can be either 'current', whereby the current sorting of the table is used, or + * 'original' whereby the original order the data was read into the table is used. + * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page + * ("current") or not ("all"). If 'current' is given, then order is assumed to be + * 'current' and filter is 'applied', regardless of what they might be given as. + * @returns {array} Data for the matched elements. If any elements, as a result of the + * selector, were not TR, TD or TH elements in the DataTable, they will have a null + * entry in the array. + * @dtopt API + * @deprecated Since v1.10 * - * Note that the `DataTable` object is not a global variable but is aliased - * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may - * be accessed. + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Get the data from the first row in the table + * var data = oTable._('tr:first'); * - * @class - * @param {object} [init={}] Configuration object for DataTables. Options - * are defined by {@link DataTable.defaults} - * @requires jQuery 1.7+ + * // Do something useful with the data + * alert( "First cell is: "+data[0] ); + * } ); * * @example - * // Basic initialisation - * $(document).ready( function { - * $('#example').dataTable(); - * } ); + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Filter to 'Webkit' and get all data for + * oTable.fnFilter('Webkit'); + * var data = oTable._('tr', {"search": "applied"}); + * + * // Do something with the data + * alert( data.length+" rows matched the search" ); + * } ); + */ + this._ = function (sSelector, oOpts) { + return this.api(true).rows(sSelector, oOpts).data(); + }; + + /** + * Create a DataTables Api instance, with the currently selected tables for + * the Api's context. + * @param {boolean} [traditional=false] Set the API instance's context to be + * only the table referred to by the `DataTable.ext.iApiIndex` option, as was + * used in the API presented by DataTables 1.9- (i.e. the traditional mode), + * or if all tables captured in the jQuery object should be used. + * @return {DataTables.Api} + */ + this.api = function (traditional) { + return traditional + ? new _Api(_fnSettingsFromNode(this[_ext.iApiIndex])) + : new _Api(this); + }; + + /** + * Add a single new row or multiple rows of data to the table. Please note + * that this is suitable for client-side processing only - if you are using + * server-side processing (i.e. "bServerSide": true), then to add data, you + * must add it to the data source, i.e. the server-side, through an Ajax call. + * @param {array|object} data The data to be added to the table. This can be: + *
    + *
  • 1D array of data - add a single row with the data provided
  • + *
  • 2D array of arrays - add multiple rows in a single call
  • + *
  • object - data object when using mData
  • + *
  • array of objects - multiple data objects when using mData
  • + *
+ * @param {bool} [redraw=true] redraw the table or not + * @returns {array} An array of integers, representing the list of indexes in + * aoData ({@link DataTable.models.oSettings}) that have been added to + * the table. + * @dtopt API + * @deprecated Since v1.10 * * @example - * // Initialisation with configuration options - in this case, disable - * // pagination and sorting. - * $(document).ready( function { - * $('#example').dataTable( { - * "paginate": false, - * "sort": false - * } ); - * } ); - */ - var DataTable = function ( options ) - { - /** - * Perform a jQuery selector action on the table's TR elements (from the tbody) and - * return the resulting jQuery object. - * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on - * @param {object} [oOpts] Optional parameters for modifying the rows to be included - * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter - * criterion ("applied") or all TR elements (i.e. no filter). - * @param {string} [oOpts.order=current] Order of the TR elements in the processed array. - * Can be either 'current', whereby the current sorting of the table is used, or - * 'original' whereby the original order the data was read into the table is used. - * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page - * ("current") or not ("all"). If 'current' is given, then order is assumed to be - * 'current' and filter is 'applied', regardless of what they might be given as. - * @returns {object} jQuery object, filtered by the given selector. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Highlight every second row - * oTable.$('tr:odd').css('backgroundColor', 'blue'); - * } ); - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Filter to rows with 'Webkit' in them, add a background colour and then - * // remove the filter, thus highlighting the 'Webkit' rows only. - * oTable.fnFilter('Webkit'); - * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue'); - * oTable.fnFilter(''); - * } ); - */ - this.$ = function ( sSelector, oOpts ) - { - return this.api(true).$( sSelector, oOpts ); - }; + * // Global var for counter + * var giCount = 2; + * + * $(document).ready(function() { + * $('#example').dataTable(); + * } ); + * + * function fnClickAddRow() { + * $('#example').dataTable().fnAddData( [ + * giCount+".1", + * giCount+".2", + * giCount+".3", + * giCount+".4" ] + * ); + * + * giCount++; + * } + */ + this.fnAddData = function (data, redraw) { + var api = this.api(true); + /* Check if we want to add multiple rows or not */ + var rows = + $.isArray(data) && ($.isArray(data[0]) || $.isPlainObject(data[0])) + ? api.rows.add(data) + : api.row.add(data); - /** - * Almost identical to $ in operation, but in this case returns the data for the matched - * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes - * rather than any descendants, so the data can be obtained for the row/cell. If matching - * rows are found, the data returned is the original data array/object that was used to - * create the row (or a generated array if from a DOM source). - * - * This method is often useful in-combination with $ where both functions are given the - * same parameters and the array indexes will match identically. - * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on - * @param {object} [oOpts] Optional parameters for modifying the rows to be included - * @param {string} [oOpts.filter=none] Select elements that meet the current filter - * criterion ("applied") or all elements (i.e. no filter). - * @param {string} [oOpts.order=current] Order of the data in the processed array. - * Can be either 'current', whereby the current sorting of the table is used, or - * 'original' whereby the original order the data was read into the table is used. - * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page - * ("current") or not ("all"). If 'current' is given, then order is assumed to be - * 'current' and filter is 'applied', regardless of what they might be given as. - * @returns {array} Data for the matched elements. If any elements, as a result of the - * selector, were not TR, TD or TH elements in the DataTable, they will have a null - * entry in the array. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Get the data from the first row in the table - * var data = oTable._('tr:first'); - * - * // Do something useful with the data - * alert( "First cell is: "+data[0] ); - * } ); - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Filter to 'Webkit' and get all data for - * oTable.fnFilter('Webkit'); - * var data = oTable._('tr', {"search": "applied"}); - * - * // Do something with the data - * alert( data.length+" rows matched the search" ); - * } ); - */ - this._ = function ( sSelector, oOpts ) - { - return this.api(true).rows( sSelector, oOpts ).data(); - }; + if (redraw === undefined || redraw) { + api.draw(); + } + return rows.flatten().toArray(); + }; - /** - * Create a DataTables Api instance, with the currently selected tables for - * the Api's context. - * @param {boolean} [traditional=false] Set the API instance's context to be - * only the table referred to by the `DataTable.ext.iApiIndex` option, as was - * used in the API presented by DataTables 1.9- (i.e. the traditional mode), - * or if all tables captured in the jQuery object should be used. - * @return {DataTables.Api} - */ - this.api = function ( traditional ) - { - return traditional ? - new _Api( - _fnSettingsFromNode( this[ _ext.iApiIndex ] ) - ) : - new _Api( this ); - }; + /** + * This function will make DataTables recalculate the column sizes, based on the data + * contained in the table and the sizes applied to the columns (in the DOM, CSS or + * through the sWidth parameter). This can be useful when the width of the table's + * parent element changes (for example a window resize). + * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable( { + * "sScrollY": "200px", + * "bPaginate": false + * } ); + * + * $(window).bind('resize', function () { + * oTable.fnAdjustColumnSizing(); + * } ); + * } ); + */ + this.fnAdjustColumnSizing = function (bRedraw) { + var api = this.api(true).columns.adjust(); + var settings = api.settings()[0]; + var scroll = settings.oScroll; + + if (bRedraw === undefined || bRedraw) { + api.draw(false); + } else if (scroll.sX !== '' || scroll.sY !== '') { + /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */ + _fnScrollDraw(settings); + } + }; + /** + * Quickly and simply clear a table + * @param {bool} [bRedraw=true] redraw the table or not + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...) + * oTable.fnClearTable(); + * } ); + */ + this.fnClearTable = function (bRedraw) { + var api = this.api(true).clear(); - /** - * Add a single new row or multiple rows of data to the table. Please note - * that this is suitable for client-side processing only - if you are using - * server-side processing (i.e. "bServerSide": true), then to add data, you - * must add it to the data source, i.e. the server-side, through an Ajax call. - * @param {array|object} data The data to be added to the table. This can be: - *
    - *
  • 1D array of data - add a single row with the data provided
  • - *
  • 2D array of arrays - add multiple rows in a single call
  • - *
  • object - data object when using mData
  • - *
  • array of objects - multiple data objects when using mData
  • - *
- * @param {bool} [redraw=true] redraw the table or not - * @returns {array} An array of integers, representing the list of indexes in - * aoData ({@link DataTable.models.oSettings}) that have been added to - * the table. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * // Global var for counter - * var giCount = 2; - * - * $(document).ready(function() { - * $('#example').dataTable(); - * } ); - * - * function fnClickAddRow() { - * $('#example').dataTable().fnAddData( [ - * giCount+".1", - * giCount+".2", - * giCount+".3", - * giCount+".4" ] - * ); - * - * giCount++; - * } - */ - this.fnAddData = function( data, redraw ) - { - var api = this.api( true ); + if (bRedraw === undefined || bRedraw) { + api.draw(); + } + }; - /* Check if we want to add multiple rows or not */ - var rows = $.isArray(data) && ( $.isArray(data[0]) || $.isPlainObject(data[0]) ) ? - api.rows.add( data ) : - api.row.add( data ); + /** + * The exact opposite of 'opening' a row, this function will close any rows which + * are currently 'open'. + * @param {node} nTr the table row to 'close' + * @returns {int} 0 on success, or 1 if failed (can't find the row) + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable; + * + * // 'open' an information row when a row is clicked on + * $('#example tbody tr').click( function () { + * if ( oTable.fnIsOpen(this) ) { + * oTable.fnClose( this ); + * } else { + * oTable.fnOpen( this, "Temporary row opened", "info_row" ); + * } + * } ); + * + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnClose = function (nTr) { + this.api(true).row(nTr).child.hide(); + }; - if ( redraw === undefined || redraw ) { - api.draw(); - } + /** + * Remove a row for the table + * @param {mixed} target The index of the row from aoData to be deleted, or + * the TR element you want to delete + * @param {function|null} [callBack] Callback function + * @param {bool} [redraw=true] Redraw the table or not + * @returns {array} The row that was deleted + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Immediately remove the first row + * oTable.fnDeleteRow( 0 ); + * } ); + */ + this.fnDeleteRow = function (target, callback, redraw) { + var api = this.api(true); + var rows = api.rows(target); + var settings = rows.settings()[0]; + var data = settings.aoData[rows[0][0]]; - return rows.flatten().toArray(); - }; + rows.remove(); + if (callback) { + callback.call(this, settings, data); + } - /** - * This function will make DataTables recalculate the column sizes, based on the data - * contained in the table and the sizes applied to the columns (in the DOM, CSS or - * through the sWidth parameter). This can be useful when the width of the table's - * parent element changes (for example a window resize). - * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable( { - * "sScrollY": "200px", - * "bPaginate": false - * } ); - * - * $(window).bind('resize', function () { - * oTable.fnAdjustColumnSizing(); - * } ); - * } ); - */ - this.fnAdjustColumnSizing = function ( bRedraw ) - { - var api = this.api( true ).columns.adjust(); - var settings = api.settings()[0]; - var scroll = settings.oScroll; - - if ( bRedraw === undefined || bRedraw ) { - api.draw( false ); - } - else if ( scroll.sX !== "" || scroll.sY !== "" ) { - /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */ - _fnScrollDraw( settings ); - } - }; + if (redraw === undefined || redraw) { + api.draw(); + } + return data; + }; - /** - * Quickly and simply clear a table - * @param {bool} [bRedraw=true] redraw the table or not - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...) - * oTable.fnClearTable(); - * } ); - */ - this.fnClearTable = function( bRedraw ) - { - var api = this.api( true ).clear(); + /** + * Restore the table to it's original state in the DOM by removing all of DataTables + * enhancements, alterations to the DOM structure of the table and event listeners. + * @param {boolean} [remove=false] Completely remove the table from the DOM + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * // This example is fairly pointless in reality, but shows how fnDestroy can be used + * var oTable = $('#example').dataTable(); + * oTable.fnDestroy(); + * } ); + */ + this.fnDestroy = function (remove) { + this.api(true).destroy(remove); + }; - if ( bRedraw === undefined || bRedraw ) { - api.draw(); - } - }; + /** + * Redraw the table + * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Re-draw the table - you wouldn't want to do it here, but it's an example :-) + * oTable.fnDraw(); + * } ); + */ + this.fnDraw = function (complete) { + // Note that this isn't an exact match to the old call to _fnDraw - it takes + // into account the new data, but can hold position. + this.api(true).draw(complete); + }; + /** + * Filter the input based on data + * @param {string} sInput String to filter the table on + * @param {int|null} [iColumn] Column to limit filtering to + * @param {bool} [bRegex=false] Treat as regular expression or not + * @param {bool} [bSmart=true] Perform smart filtering or not + * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es) + * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false) + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Sometime later - filter... + * oTable.fnFilter( 'test string' ); + * } ); + */ + this.fnFilter = function ( + sInput, + iColumn, + bRegex, + bSmart, + bShowGlobal, + bCaseInsensitive + ) { + var api = this.api(true); + + if (iColumn === null || iColumn === undefined) { + api.search(sInput, bRegex, bSmart, bCaseInsensitive); + } else { + api.column(iColumn).search(sInput, bRegex, bSmart, bCaseInsensitive); + } + + api.draw(); + }; - /** - * The exact opposite of 'opening' a row, this function will close any rows which - * are currently 'open'. - * @param {node} nTr the table row to 'close' - * @returns {int} 0 on success, or 1 if failed (can't find the row) - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable; - * - * // 'open' an information row when a row is clicked on - * $('#example tbody tr').click( function () { - * if ( oTable.fnIsOpen(this) ) { - * oTable.fnClose( this ); - * } else { - * oTable.fnOpen( this, "Temporary row opened", "info_row" ); - * } - * } ); - * - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnClose = function( nTr ) - { - this.api( true ).row( nTr ).child.hide(); - }; + /** + * Get the data for the whole table, an individual row or an individual cell based on the + * provided parameters. + * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as + * a TR node then the data source for the whole row will be returned. If given as a + * TD/TH cell node then iCol will be automatically calculated and the data for the + * cell returned. If given as an integer, then this is treated as the aoData internal + * data index for the row (see fnGetPosition) and the data for that row used. + * @param {int} [col] Optional column index that you want the data of. + * @returns {array|object|string} If mRow is undefined, then the data for all rows is + * returned. If mRow is defined, just data for that row, and is iCol is + * defined, only data for the designated cell is returned. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * // Row data + * $(document).ready(function() { + * oTable = $('#example').dataTable(); + * + * oTable.$('tr').click( function () { + * var data = oTable.fnGetData( this ); + * // ... do something with the array / object of data for the row + * } ); + * } ); + * + * @example + * // Individual cell data + * $(document).ready(function() { + * oTable = $('#example').dataTable(); + * + * oTable.$('td').click( function () { + * var sData = oTable.fnGetData( this ); + * alert( 'The cell clicked on had the value of '+sData ); + * } ); + * } ); + */ + this.fnGetData = function (src, col) { + var api = this.api(true); + if (src !== undefined) { + var type = src.nodeName ? src.nodeName.toLowerCase() : ''; - /** - * Remove a row for the table - * @param {mixed} target The index of the row from aoData to be deleted, or - * the TR element you want to delete - * @param {function|null} [callBack] Callback function - * @param {bool} [redraw=true] Redraw the table or not - * @returns {array} The row that was deleted - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Immediately remove the first row - * oTable.fnDeleteRow( 0 ); - * } ); - */ - this.fnDeleteRow = function( target, callback, redraw ) - { - var api = this.api( true ); - var rows = api.rows( target ); - var settings = rows.settings()[0]; - var data = settings.aoData[ rows[0][0] ]; + return col !== undefined || type === 'td' || type === 'th' + ? api.cell(src, col).data() + : api.row(src).data() || null; + } - rows.remove(); + return api.data().toArray(); + }; - if ( callback ) { - callback.call( this, settings, data ); - } + /** + * Get an array of the TR nodes that are used in the table's body. Note that you will + * typically want to use the '$' API method in preference to this as it is more + * flexible. + * @param {int} [iRow] Optional row index for the TR element you want + * @returns {array|node} If iRow is undefined, returns an array of all TR elements + * in the table's body, or iRow is defined, just the TR element requested. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Get the nodes from the table + * var nNodes = oTable.fnGetNodes( ); + * } ); + */ + this.fnGetNodes = function (iRow) { + var api = this.api(true); - if ( redraw === undefined || redraw ) { - api.draw(); - } + return iRow !== undefined + ? api.row(iRow).node() + : api.rows().nodes().flatten().toArray(); + }; - return data; - }; + /** + * Get the array indexes of a particular cell from it's DOM element + * and column index including hidden columns + * @param {node} node this can either be a TR, TD or TH in the table's body + * @returns {int} If nNode is given as a TR, then a single index is returned, or + * if given as a cell, an array of [row index, column index (visible), + * column index (all)] is given. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * $('#example tbody td').click( function () { + * // Get the position of the current data from the node + * var aPos = oTable.fnGetPosition( this ); + * + * // Get the data array for this row + * var aData = oTable.fnGetData( aPos[0] ); + * + * // Update the data array and return the value + * aData[ aPos[1] ] = 'clicked'; + * this.innerHTML = 'clicked'; + * } ); + * + * // Init DataTables + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnGetPosition = function (node) { + var api = this.api(true); + var nodeName = node.nodeName.toUpperCase(); + + if (nodeName === 'TR') { + return api.row(node).index(); + } else if (nodeName === 'TD' || nodeName === 'TH') { + var cell = api.cell(node).index(); + + return [cell.row, cell.columnVisible, cell.column]; + } + return null; + }; + /** + * Check to see if a row is 'open' or not. + * @param {node} nTr the table row to check + * @returns {boolean} true if the row is currently open, false otherwise + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable; + * + * // 'open' an information row when a row is clicked on + * $('#example tbody tr').click( function () { + * if ( oTable.fnIsOpen(this) ) { + * oTable.fnClose( this ); + * } else { + * oTable.fnOpen( this, "Temporary row opened", "info_row" ); + * } + * } ); + * + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnIsOpen = function (nTr) { + return this.api(true).row(nTr).child.isShown(); + }; - /** - * Restore the table to it's original state in the DOM by removing all of DataTables - * enhancements, alterations to the DOM structure of the table and event listeners. - * @param {boolean} [remove=false] Completely remove the table from the DOM - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * // This example is fairly pointless in reality, but shows how fnDestroy can be used - * var oTable = $('#example').dataTable(); - * oTable.fnDestroy(); - * } ); - */ - this.fnDestroy = function ( remove ) - { - this.api( true ).destroy( remove ); - }; + /** + * This function will place a new row directly after a row which is currently + * on display on the page, with the HTML contents that is passed into the + * function. This can be used, for example, to ask for confirmation that a + * particular record should be deleted. + * @param {node} nTr The table row to 'open' + * @param {string|node|jQuery} mHtml The HTML to put into the row + * @param {string} sClass Class to give the new TD cell + * @returns {node} The row opened. Note that if the table row passed in as the + * first parameter, is not found in the table, this method will silently + * return. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable; + * + * // 'open' an information row when a row is clicked on + * $('#example tbody tr').click( function () { + * if ( oTable.fnIsOpen(this) ) { + * oTable.fnClose( this ); + * } else { + * oTable.fnOpen( this, "Temporary row opened", "info_row" ); + * } + * } ); + * + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnOpen = function (nTr, mHtml, sClass) { + return this.api(true).row(nTr).child(mHtml, sClass).show().child()[0]; + }; + /** + * Change the pagination - provides the internal logic for pagination in a simple API + * function. With this function you can have a DataTables table go to the next, + * previous, first or last pages. + * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last" + * or page number to jump to (integer), note that page 0 is the first page. + * @param {bool} [bRedraw=true] Redraw the table or not + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * oTable.fnPageChange( 'next' ); + * } ); + */ + this.fnPageChange = function (mAction, bRedraw) { + var api = this.api(true).page(mAction); - /** - * Redraw the table - * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Re-draw the table - you wouldn't want to do it here, but it's an example :-) - * oTable.fnDraw(); - * } ); - */ - this.fnDraw = function( complete ) - { - // Note that this isn't an exact match to the old call to _fnDraw - it takes - // into account the new data, but can hold position. - this.api( true ).draw( complete ); - }; + if (bRedraw === undefined || bRedraw) { + api.draw(false); + } + }; + /** + * Show a particular column + * @param {int} iCol The column whose display should be changed + * @param {bool} bShow Show (true) or hide (false) the column + * @param {bool} [bRedraw=true] Redraw the table or not + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Hide the second column after initialisation + * oTable.fnSetColumnVis( 1, false ); + * } ); + */ + this.fnSetColumnVis = function (iCol, bShow, bRedraw) { + var api = this.api(true).column(iCol).visible(bShow); - /** - * Filter the input based on data - * @param {string} sInput String to filter the table on - * @param {int|null} [iColumn] Column to limit filtering to - * @param {bool} [bRegex=false] Treat as regular expression or not - * @param {bool} [bSmart=true] Perform smart filtering or not - * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es) - * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false) - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Sometime later - filter... - * oTable.fnFilter( 'test string' ); - * } ); - */ - this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive ) - { - var api = this.api( true ); + if (bRedraw === undefined || bRedraw) { + api.columns.adjust().draw(); + } + }; - if ( iColumn === null || iColumn === undefined ) { - api.search( sInput, bRegex, bSmart, bCaseInsensitive ); - } - else { - api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive ); - } + /** + * Get the settings for a particular table for external manipulation + * @returns {object} DataTables settings object. See + * {@link DataTable.models.oSettings} + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * var oSettings = oTable.fnSettings(); + * + * // Show an example parameter from the settings + * alert( oSettings._iDisplayStart ); + * } ); + */ + this.fnSettings = function () { + return _fnSettingsFromNode(this[_ext.iApiIndex]); + }; - api.draw(); - }; + /** + * Sort the table by a particular column + * @param {int} iCol the data index to sort on. Note that this will not match the + * 'display index' if you have hidden data entries + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Sort immediately with columns 0 and 1 + * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] ); + * } ); + */ + this.fnSort = function (aaSort) { + this.api(true).order(aaSort).draw(); + }; + /** + * Attach a sort listener to an element for a given column + * @param {node} nNode the element to attach the sort listener to + * @param {int} iColumn the column that a click on this node will sort on + * @param {function} [fnCallback] callback function when sort is run + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Sort on column 1, when 'sorter' is clicked on + * oTable.fnSortListener( document.getElementById('sorter'), 1 ); + * } ); + */ + this.fnSortListener = function (nNode, iColumn, fnCallback) { + this.api(true).order.listener(nNode, iColumn, fnCallback); + }; - /** - * Get the data for the whole table, an individual row or an individual cell based on the - * provided parameters. - * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as - * a TR node then the data source for the whole row will be returned. If given as a - * TD/TH cell node then iCol will be automatically calculated and the data for the - * cell returned. If given as an integer, then this is treated as the aoData internal - * data index for the row (see fnGetPosition) and the data for that row used. - * @param {int} [col] Optional column index that you want the data of. - * @returns {array|object|string} If mRow is undefined, then the data for all rows is - * returned. If mRow is defined, just data for that row, and is iCol is - * defined, only data for the designated cell is returned. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * // Row data - * $(document).ready(function() { - * oTable = $('#example').dataTable(); - * - * oTable.$('tr').click( function () { - * var data = oTable.fnGetData( this ); - * // ... do something with the array / object of data for the row - * } ); - * } ); - * - * @example - * // Individual cell data - * $(document).ready(function() { - * oTable = $('#example').dataTable(); - * - * oTable.$('td').click( function () { - * var sData = oTable.fnGetData( this ); - * alert( 'The cell clicked on had the value of '+sData ); - * } ); - * } ); - */ - this.fnGetData = function( src, col ) - { - var api = this.api( true ); + /** + * Update a table cell or row - this method will accept either a single value to + * update the cell with, an array of values with one element for each column or + * an object in the same format as the original data source. The function is + * self-referencing in order to make the multi column updates easier. + * @param {object|array|string} mData Data to update the cell/row with + * @param {node|int} mRow TR element you want to update or the aoData index + * @param {int} [iColumn] The column to update, give as null or undefined to + * update a whole row. + * @param {bool} [bRedraw=true] Redraw the table or not + * @param {bool} [bAction=true] Perform pre-draw actions or not + * @returns {int} 0 on success, 1 on error + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell + * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row + * } ); + */ + this.fnUpdate = function (mData, mRow, iColumn, bRedraw, bAction) { + var api = this.api(true); + + if (iColumn === undefined || iColumn === null) { + api.row(mRow).data(mData); + } else { + api.cell(mRow, iColumn).data(mData); + } + + if (bAction === undefined || bAction) { + api.columns.adjust(); + } + + if (bRedraw === undefined || bRedraw) { + api.draw(); + } + return 0; + }; - if ( src !== undefined ) { - var type = src.nodeName ? src.nodeName.toLowerCase() : ''; + /** + * Provide a common method for plug-ins to check the version of DataTables being used, in order + * to ensure compatibility. + * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the + * formats "X" and "X.Y" are also acceptable. + * @returns {boolean} true if this version of DataTables is greater or equal to the required + * version, or false if this version of DataTales is not suitable + * @method + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * alert( oTable.fnVersionCheck( '1.9.0' ) ); + * } ); + */ + this.fnVersionCheck = _ext.fnVersionCheck; - return col !== undefined || type == 'td' || type == 'th' ? - api.cell( src, col ).data() : - api.row( src ).data() || null; - } + var _that = this; + var emptyInit = options === undefined; + var len = this.length; - return api.data().toArray(); - }; + if (emptyInit) { + options = {}; + } + this.oApi = this.internal = _ext.internal; - /** - * Get an array of the TR nodes that are used in the table's body. Note that you will - * typically want to use the '$' API method in preference to this as it is more - * flexible. - * @param {int} [iRow] Optional row index for the TR element you want - * @returns {array|node} If iRow is undefined, returns an array of all TR elements - * in the table's body, or iRow is defined, just the TR element requested. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Get the nodes from the table - * var nNodes = oTable.fnGetNodes( ); - * } ); - */ - this.fnGetNodes = function( iRow ) - { - var api = this.api( true ); + // Extend with old style plug-in API methods + for (var fn in DataTable.ext.internal) { + if (fn) { + this[fn] = _fnExternApiFunc(fn); + } + } - return iRow !== undefined ? - api.row( iRow ).node() : - api.rows().nodes().flatten().toArray(); + this.each(function () { + // For each initialisation we want to give it a clean initialisation + // object that can be bashed around + var o = {}; + var oInit = + len > 1 // optimisation for single table case + ? _fnExtend(o, options, true) + : options; + + /*global oInit,_that,emptyInit*/ + var i = 0, + iLen, + j, + jLen, + k, + kLen; + var sId = this.getAttribute('id'); + var bInitHandedOff = false; + var defaults = DataTable.defaults; + var $this = $(this); + + /* Sanity check */ + if (this.nodeName.toLowerCase() !== 'table') { + _fnLog( + null, + 0, + 'Non-table node initialisation (' + this.nodeName + ')', + 2 + ); + return; + } + + /* Backwards compatibility for the defaults */ + _fnCompatOpts(defaults); + _fnCompatCols(defaults.column); + + /* Convert the camel-case defaults to Hungarian */ + _fnCamelToHungarian(defaults, defaults, true); + _fnCamelToHungarian(defaults.column, defaults.column, true); + + /* Setting up the initialisation object */ + _fnCamelToHungarian(defaults, $.extend(oInit, $this.data())); + + /* Check to see if we are re-initialising a table */ + var allSettings = DataTable.settings; + for (i = 0, iLen = allSettings.length; i < iLen; i++) { + var s = allSettings[i]; + + /* Base check on table node */ + if ( + s.nTable === this || + s.nTHead.parentNode === this || + (s.nTFoot && s.nTFoot.parentNode === this) + ) { + var bRetrieve = + oInit.bRetrieve !== undefined + ? oInit.bRetrieve + : defaults.bRetrieve; + var bDestroy = + oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy; + + if (emptyInit || bRetrieve) { + return s.oInstance; + } else if (bDestroy) { + s.oInstance.fnDestroy(); + break; + } else { + _fnLog(s, 0, 'Cannot reinitialise DataTable', 3); + return; + } + } + + /* If the element we are initialising has the same ID as a table which was previously + * initialised, but the table nodes don't match (from before) then we destroy the old + * instance by simply deleting it. This is under the assumption that the table has been + * destroyed by other methods. Anyone using non-id selectors will need to do this manually + */ + if (s.sTableId === this.id) { + allSettings.splice(i, 1); + break; + } + } + + /* Ensure the table has an ID - required for accessibility */ + if (sId === null || sId === '') { + sId = 'DataTables_Table_' + DataTable.ext._unique++; + this.id = sId; + } + + /* Create the settings object for this table and set some of the default parameters */ + var oSettings = $.extend(true, {}, DataTable.models.oSettings, { + sDestroyWidth: $this[0].style.width, + sInstance: sId, + sTableId: sId, + }); + oSettings.nTable = this; + oSettings.oApi = _that.internal; + oSettings.oInit = oInit; + + allSettings.push(oSettings); + + // Need to add the instance after the instance after the settings object has been added + // to the settings array, so we can self reference the table instance if more than one + oSettings.oInstance = _that.length === 1 ? _that : $this.dataTable(); + + // Backwards compatibility, before we apply all the defaults + _fnCompatOpts(oInit); + + if (oInit.oLanguage) { + _fnLanguageCompat(oInit.oLanguage); + } + + // If the length menu is given, but the init display length is not, use the length menu + if (oInit.aLengthMenu && !oInit.iDisplayLength) { + oInit.iDisplayLength = $.isArray(oInit.aLengthMenu[0]) + ? oInit.aLengthMenu[0][0] + : oInit.aLengthMenu[0]; + } + + // Apply the defaults and init options to make a single init object will all + // options defined from defaults and instance options. + oInit = _fnExtend($.extend(true, {}, defaults), oInit); + + // Map the initialisation options onto the settings object + _fnMap(oSettings.oFeatures, oInit, [ + 'bPaginate', + 'bLengthChange', + 'bFilter', + 'bSort', + 'bSortMulti', + 'bInfo', + 'bProcessing', + 'bAutoWidth', + 'bSortClasses', + 'bServerSide', + 'bDeferRender', + ]); + _fnMap(oSettings, oInit, [ + 'asStripeClasses', + 'ajax', + 'fnServerData', + 'fnFormatNumber', + 'sServerMethod', + 'aaSorting', + 'aaSortingFixed', + 'aLengthMenu', + 'sPaginationType', + 'sAjaxSource', + 'sAjaxDataProp', + 'iStateDuration', + 'sDom', + 'bSortCellsTop', + 'iTabIndex', + 'fnStateLoadCallback', + 'fnStateSaveCallback', + 'renderer', + 'searchDelay', + 'rowId', + ['iCookieDuration', 'iStateDuration'], // backwards compat + ['oSearch', 'oPreviousSearch'], + ['aoSearchCols', 'aoPreSearchCols'], + ['iDisplayLength', '_iDisplayLength'], + ['bJQueryUI', 'bJUI'], + ]); + _fnMap(oSettings.oScroll, oInit, [ + ['sScrollX', 'sX'], + ['sScrollXInner', 'sXInner'], + ['sScrollY', 'sY'], + ['bScrollCollapse', 'bCollapse'], + ]); + _fnMap(oSettings.oLanguage, oInit, 'fnInfoCallback'); + + /* Callback functions which are array driven */ + _fnCallbackReg(oSettings, 'aoDrawCallback', oInit.fnDrawCallback, 'user'); + _fnCallbackReg(oSettings, 'aoServerParams', oInit.fnServerParams, 'user'); + _fnCallbackReg( + oSettings, + 'aoStateSaveParams', + oInit.fnStateSaveParams, + 'user' + ); + _fnCallbackReg( + oSettings, + 'aoStateLoadParams', + oInit.fnStateLoadParams, + 'user' + ); + _fnCallbackReg(oSettings, 'aoStateLoaded', oInit.fnStateLoaded, 'user'); + _fnCallbackReg(oSettings, 'aoRowCallback', oInit.fnRowCallback, 'user'); + _fnCallbackReg( + oSettings, + 'aoRowCreatedCallback', + oInit.fnCreatedRow, + 'user' + ); + _fnCallbackReg( + oSettings, + 'aoHeaderCallback', + oInit.fnHeaderCallback, + 'user' + ); + _fnCallbackReg( + oSettings, + 'aoFooterCallback', + oInit.fnFooterCallback, + 'user' + ); + _fnCallbackReg(oSettings, 'aoInitComplete', oInit.fnInitComplete, 'user'); + _fnCallbackReg( + oSettings, + 'aoPreDrawCallback', + oInit.fnPreDrawCallback, + 'user' + ); + + oSettings.rowIdFn = _fnGetObjectDataFn(oInit.rowId); + + /* Browser support detection */ + _fnBrowserDetect(oSettings); + + var oClasses = oSettings.oClasses; + + // @todo Remove in 1.11 + if (oInit.bJQueryUI) { + /* Use the JUI classes object for display. You could clone the oStdClasses object if + * you want to have multiple tables with multiple independent classes + */ + $.extend(oClasses, DataTable.ext.oJUIClasses, oInit.oClasses); + + if (oInit.sDom === defaults.sDom && defaults.sDom === 'lfrtip') { + /* Set the DOM to use a layout suitable for jQuery UI's theming */ + oSettings.sDom = '<"H"lfr>t<"F"ip>'; + } + + if (!oSettings.renderer) { + oSettings.renderer = 'jqueryui'; + } else if ( + $.isPlainObject(oSettings.renderer) && + !oSettings.renderer.header + ) { + oSettings.renderer.header = 'jqueryui'; + } + } else { + $.extend(oClasses, DataTable.ext.classes, oInit.oClasses); + } + $this.addClass(oClasses.sTable); + + if (oSettings.iInitDisplayStart === undefined) { + /* Display start point, taking into account the save saving */ + oSettings.iInitDisplayStart = oInit.iDisplayStart; + oSettings._iDisplayStart = oInit.iDisplayStart; + } + + if (oInit.iDeferLoading !== null) { + oSettings.bDeferLoading = true; + var tmp = $.isArray(oInit.iDeferLoading); + oSettings._iRecordsDisplay = tmp + ? oInit.iDeferLoading[0] + : oInit.iDeferLoading; + oSettings._iRecordsTotal = tmp + ? oInit.iDeferLoading[1] + : oInit.iDeferLoading; + } + + /* Language definitions */ + var oLanguage = oSettings.oLanguage; + $.extend(true, oLanguage, oInit.oLanguage); + + if (oLanguage.sUrl !== '') { + /* Get the language definitions from a file - because this Ajax call makes the language + * get async to the remainder of this function we use bInitHandedOff to indicate that + * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor + */ + $.ajax({ + dataType: 'json', + url: oLanguage.sUrl, + success: function (json) { + _fnLanguageCompat(json); + _fnCamelToHungarian(defaults.oLanguage, json); + $.extend(true, oLanguage, json); + _fnInitialise(oSettings); + }, + error: function () { + // Error occurred loading language file, continue on as best we can + _fnInitialise(oSettings); + }, + }); + bInitHandedOff = true; + } + + /* + * Stripes + */ + if (oInit.asStripeClasses === null) { + oSettings.asStripeClasses = [oClasses.sStripeOdd, oClasses.sStripeEven]; + } + + /* Remove row stripe classes if they are already on the table row */ + var stripeClasses = oSettings.asStripeClasses; + var rowOne = $this.children('tbody').find('tr').eq(0); + if ( + $.inArray( + true, + $.map(stripeClasses, function (el, i) { + return rowOne.hasClass(el); + }) + ) !== -1 + ) { + $('tbody tr', this).removeClass(stripeClasses.join(' ')); + oSettings.asDestroyStripes = stripeClasses.slice(); + } + + /* + * Columns + * See if we should load columns automatically or use defined ones + */ + var anThs = []; + var aoColumnsInit; + var nThead = this.getElementsByTagName('thead'); + if (nThead.length !== 0) { + _fnDetectHeader(oSettings.aoHeader, nThead[0]); + anThs = _fnGetUniqueThs(oSettings); + } + + /* If not given a column array, generate one with nulls */ + if (oInit.aoColumns === null) { + aoColumnsInit = []; + for (i = 0, iLen = anThs.length; i < iLen; i++) { + aoColumnsInit.push(null); + } + } else { + aoColumnsInit = oInit.aoColumns; + } + + /* Add the columns */ + for (i = 0, iLen = aoColumnsInit.length; i < iLen; i++) { + _fnAddColumn(oSettings, anThs ? anThs[i] : null); + } + + /* Apply the column definitions */ + _fnApplyColumnDefs( + oSettings, + oInit.aoColumnDefs, + aoColumnsInit, + function (iCol, oDef) { + _fnColumnOptions(oSettings, iCol, oDef); + } + ); + + /* HTML5 attribute detection - build an mData object automatically if the + * attributes are found + */ + if (rowOne.length) { + var a = function (cell, name) { + return cell.getAttribute('data-' + name) !== null ? name : null; }; + $(rowOne[0]) + .children('th, td') + .each(function (i, cell) { + var col = oSettings.aoColumns[i]; + + if (col.mData === i) { + var sort = a(cell, 'sort') || a(cell, 'order'); + var filter = a(cell, 'filter') || a(cell, 'search'); + + if (sort !== null || filter !== null) { + col.mData = { + _: i + '.display', + sort: sort !== null ? i + '.@data-' + sort : undefined, + type: sort !== null ? i + '.@data-' + sort : undefined, + filter: filter !== null ? i + '.@data-' + filter : undefined, + }; - /** - * Get the array indexes of a particular cell from it's DOM element - * and column index including hidden columns - * @param {node} node this can either be a TR, TD or TH in the table's body - * @returns {int} If nNode is given as a TR, then a single index is returned, or - * if given as a cell, an array of [row index, column index (visible), - * column index (all)] is given. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * $('#example tbody td').click( function () { - * // Get the position of the current data from the node - * var aPos = oTable.fnGetPosition( this ); - * - * // Get the data array for this row - * var aData = oTable.fnGetData( aPos[0] ); - * - * // Update the data array and return the value - * aData[ aPos[1] ] = 'clicked'; - * this.innerHTML = 'clicked'; - * } ); - * - * // Init DataTables - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnGetPosition = function( node ) - { - var api = this.api( true ); - var nodeName = node.nodeName.toUpperCase(); + _fnColumnOptions(oSettings, i); + } + } + }); + } + + var features = oSettings.oFeatures; + + /* Must be done after everything which can be overridden by the state saving! */ + if (oInit.bStateSave) { + features.bStateSave = true; + _fnLoadState(oSettings, oInit); + _fnCallbackReg(oSettings, 'aoDrawCallback', _fnSaveState, 'state_save'); + } + + /* + * Sorting + * @todo For modularisation (1.11) this needs to do into a sort start up handler + */ + + // If aaSorting is not defined, then we use the first indicator in asSorting + // in case that has been altered, so the default sort reflects that option + if (oInit.aaSorting === undefined) { + var sorting = oSettings.aaSorting; + for (i = 0, iLen = sorting.length; i < iLen; i++) { + sorting[i][1] = oSettings.aoColumns[i].asSorting[0]; + } + } + + /* Do a first pass on the sorting classes (allows any size changes to be taken into + * account, and also will apply sorting disabled classes if disabled + */ + _fnSortingClasses(oSettings); + + if (features.bSort) { + _fnCallbackReg(oSettings, 'aoDrawCallback', function () { + if (oSettings.bSorted) { + var aSort = _fnSortFlatten(oSettings); + var sortedColumns = {}; + + $.each(aSort, function (i, val) { + sortedColumns[val.src] = val.dir; + }); + + _fnCallbackFire(oSettings, null, 'order', [ + oSettings, + aSort, + sortedColumns, + ]); + _fnSortAria(oSettings); + } + }); + } + + _fnCallbackReg( + oSettings, + 'aoDrawCallback', + function () { + if ( + oSettings.bSorted || + _fnDataSource(oSettings) === 'ssp' || + features.bDeferRender + ) { + _fnSortingClasses(oSettings); + } + }, + 'sc' + ); + + /* + * Final init + * Cache the header, body and footer as required, creating them if needed + */ + + // Work around for Webkit bug 83867 - store the caption-side before removing from doc + var captions = $this.children('caption').each(function () { + this._captionSide = $this.css('caption-side'); + }); + + var thead = $this.children('thead'); + if (thead.length === 0) { + thead = $('').appendTo(this); + } + oSettings.nTHead = thead[0]; + + var tbody = $this.children('tbody'); + if (tbody.length === 0) { + tbody = $('').appendTo(this); + } + oSettings.nTBody = tbody[0]; + + var tfoot = $this.children('tfoot'); + if ( + tfoot.length === 0 && + captions.length > 0 && + (oSettings.oScroll.sX !== '' || oSettings.oScroll.sY !== '') + ) { + // If we are a scrolling table, and no footer has been given, then we need to create + // a tfoot element for the caption element to be appended to + tfoot = $('').appendTo(this); + } + + if (tfoot.length === 0 || tfoot.children().length === 0) { + $this.addClass(oClasses.sNoFooter); + } else if (tfoot.length > 0) { + oSettings.nTFoot = tfoot[0]; + _fnDetectHeader(oSettings.aoFooter, oSettings.nTFoot); + } + + /* Check if there is data passing into the constructor */ + if (oInit.aaData) { + for (i = 0; i < oInit.aaData.length; i++) { + _fnAddData(oSettings, oInit.aaData[i]); + } + } else if ( + oSettings.bDeferLoading || + _fnDataSource(oSettings) === 'dom' + ) { + /* Grab the data from the page - only do this when deferred loading or no Ajax + * source since there is no point in reading the DOM data if we are then going + * to replace it with Ajax data + */ + _fnAddTr(oSettings, $(oSettings.nTBody).children('tr')); + } + + /* Copy the data index array */ + oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); + + /* Initialisation complete - table can be drawn */ + oSettings.bInitialised = true; + + /* Check if we need to initialise the table (it might not have been handed off to the + * language processor) + */ + if (bInitHandedOff === false) { + _fnInitialise(oSettings); + } + }); + _that = null; + return this; + }; + + /* + * It is useful to have variables which are scoped locally so only the + * DataTables functions can access them and they don't leak into global space. + * At the same time these functions are often useful over multiple files in the + * core and API, so we list, or at least document, all variables which are used + * by DataTables as private variables here. This also ensures that there is no + * clashing of variable names and that they can easily referenced for reuse. + */ + + // Defined else where + // _selector_run + // _selector_opts + // _selector_first + // _selector_row_indexes + + var _ext; // DataTable.ext + var _Api; // DataTable.Api + var _api_register; // DataTable.Api.register + var _api_registerPlural; // DataTable.Api.registerPlural + + var _re_dic = {}; + var _re_new_lines = /[\r\n]/g; + var _re_html = /<.*?>/g; + var _re_date_start = /^[\w\+\-]/; + var _re_date_end = /[\w\+\-]$/; + + // Escape regular expression special characters + var _re_escape_regex = new RegExp( + '(\\' + + [ + '/', + '.', + '*', + '+', + '?', + '|', + '(', + ')', + '[', + ']', + '{', + '}', + '\\', + '$', + '^', + '-', + ].join('|\\') + + ')', + 'g' + ); + + // http://en.wikipedia.org/wiki/Foreign_exchange_market + // - \u20BD - Russian ruble. + // - \u20a9 - South Korean Won + // - \u20BA - Turkish Lira + // - \u20B9 - Indian Rupee + // - R - Brazil (R$) and South Africa + // - fr - Swiss Franc + // - kr - Swedish krona, Norwegian krone and Danish krone + // - \u2009 is thin space and \u202F is narrow no-break space, both used in many + // standards as thousands separators. + var _re_formatted_numeric = /[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi; + + var _empty = function (d) { + return !d || d === true || d === '-' ? true : false; + }; + + var _intVal = function (s) { + var integer = parseInt(s, 10); + return !isNaN(integer) && isFinite(s) ? integer : null; + }; + + // Convert from a formatted number with characters other than `.` as the + // decimal place, to a Javascript number + var _numToDecimal = function (num, decimalPoint) { + // Cache created regular expressions for speed as this function is called often + if (!_re_dic[decimalPoint]) { + _re_dic[decimalPoint] = new RegExp(_fnEscapeRegex(decimalPoint), 'g'); + } + return typeof num === 'string' && decimalPoint !== '.' + ? num.replace(/\./g, '').replace(_re_dic[decimalPoint], '.') + : num; + }; + + var _isNumber = function (d, decimalPoint, formatted) { + var strType = typeof d === 'string'; + + // If empty return immediately so there must be a number if it is a + // formatted string (this stops the string "k", or "kr", etc being detected + // as a formatted number for currency + if (_empty(d)) { + return true; + } - if ( nodeName == 'TR' ) { - return api.row( node ).index(); - } - else if ( nodeName == 'TD' || nodeName == 'TH' ) { - var cell = api.cell( node ).index(); - - return [ - cell.row, - cell.columnVisible, - cell.column - ]; - } - return null; - }; + if (decimalPoint && strType) { + d = _numToDecimal(d, decimalPoint); + } + if (formatted && strType) { + d = d.replace(_re_formatted_numeric, ''); + } - /** - * Check to see if a row is 'open' or not. - * @param {node} nTr the table row to check - * @returns {boolean} true if the row is currently open, false otherwise - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable; - * - * // 'open' an information row when a row is clicked on - * $('#example tbody tr').click( function () { - * if ( oTable.fnIsOpen(this) ) { - * oTable.fnClose( this ); - * } else { - * oTable.fnOpen( this, "Temporary row opened", "info_row" ); - * } - * } ); - * - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnIsOpen = function( nTr ) - { - return this.api( true ).row( nTr ).child.isShown(); - }; + return !isNaN(parseFloat(d)) && isFinite(d); + }; + // A string without HTML in it can be considered to be HTML still + var _isHtml = function (d) { + return _empty(d) || typeof d === 'string'; + }; - /** - * This function will place a new row directly after a row which is currently - * on display on the page, with the HTML contents that is passed into the - * function. This can be used, for example, to ask for confirmation that a - * particular record should be deleted. - * @param {node} nTr The table row to 'open' - * @param {string|node|jQuery} mHtml The HTML to put into the row - * @param {string} sClass Class to give the new TD cell - * @returns {node} The row opened. Note that if the table row passed in as the - * first parameter, is not found in the table, this method will silently - * return. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable; - * - * // 'open' an information row when a row is clicked on - * $('#example tbody tr').click( function () { - * if ( oTable.fnIsOpen(this) ) { - * oTable.fnClose( this ); - * } else { - * oTable.fnOpen( this, "Temporary row opened", "info_row" ); - * } - * } ); - * - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnOpen = function( nTr, mHtml, sClass ) - { - return this.api( true ) - .row( nTr ) - .child( mHtml, sClass ) - .show() - .child()[0]; - }; + var _htmlNumeric = function (d, decimalPoint, formatted) { + if (_empty(d)) { + return true; + } + var html = _isHtml(d); + return !html + ? null + : _isNumber(_stripHtml(d), decimalPoint, formatted) + ? true + : null; + }; + + var _pluck = function (a, prop, prop2) { + var out = []; + var i = 0, + ien = a.length; + + // Could have the test in the loop for slightly smaller code, but speed + // is essential here + if (prop2 !== undefined) { + for (; i < ien; i++) { + if (a[i] && a[i][prop]) { + out.push(a[i][prop][prop2]); + } + } + } else { + for (; i < ien; i++) { + if (a[i]) { + out.push(a[i][prop]); + } + } + } - /** - * Change the pagination - provides the internal logic for pagination in a simple API - * function. With this function you can have a DataTables table go to the next, - * previous, first or last pages. - * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last" - * or page number to jump to (integer), note that page 0 is the first page. - * @param {bool} [bRedraw=true] Redraw the table or not - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * oTable.fnPageChange( 'next' ); - * } ); - */ - this.fnPageChange = function ( mAction, bRedraw ) - { - var api = this.api( true ).page( mAction ); - - if ( bRedraw === undefined || bRedraw ) { - api.draw(false); - } - }; + return out; + }; + + // Basically the same as _pluck, but rather than looping over `a` we use `order` + // as the indexes to pick from `a` + var _pluck_order = function (a, order, prop, prop2) { + var out = []; + var i = 0, + ien = order.length; + + // Could have the test in the loop for slightly smaller code, but speed + // is essential here + if (prop2 !== undefined) { + for (; i < ien; i++) { + if (a[order[i]][prop]) { + out.push(a[order[i]][prop][prop2]); + } + } + } else { + for (; i < ien; i++) { + out.push(a[order[i]][prop]); + } + } + return out; + }; - /** - * Show a particular column - * @param {int} iCol The column whose display should be changed - * @param {bool} bShow Show (true) or hide (false) the column - * @param {bool} [bRedraw=true] Redraw the table or not - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Hide the second column after initialisation - * oTable.fnSetColumnVis( 1, false ); - * } ); - */ - this.fnSetColumnVis = function ( iCol, bShow, bRedraw ) - { - var api = this.api( true ).column( iCol ).visible( bShow ); + var _range = function (len, start) { + var out = []; + var end; - if ( bRedraw === undefined || bRedraw ) { - api.columns.adjust().draw(); - } - }; + if (start === undefined) { + start = 0; + end = len; + } else { + end = start; + start = len; + } + for (var i = start; i < end; i++) { + out.push(i); + } - /** - * Get the settings for a particular table for external manipulation - * @returns {object} DataTables settings object. See - * {@link DataTable.models.oSettings} - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * var oSettings = oTable.fnSettings(); - * - * // Show an example parameter from the settings - * alert( oSettings._iDisplayStart ); - * } ); - */ - this.fnSettings = function() - { - return _fnSettingsFromNode( this[_ext.iApiIndex] ); - }; + return out; + }; + var _removeEmpty = function (a) { + var out = []; - /** - * Sort the table by a particular column - * @param {int} iCol the data index to sort on. Note that this will not match the - * 'display index' if you have hidden data entries - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Sort immediately with columns 0 and 1 - * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] ); - * } ); - */ - this.fnSort = function( aaSort ) - { - this.api( true ).order( aaSort ).draw(); - }; + for (var i = 0, ien = a.length; i < ien; i++) { + if (a[i]) { + // careful - will remove all falsy values! + out.push(a[i]); + } + } + return out; + }; + + var _stripHtml = function (d) { + return d.replace(_re_html, ''); + }; + + /** + * Find the unique elements in a source array. + * + * @param {array} src Source array + * @return {array} Array of unique items + * @ignore + */ + var _unique = function (src) { + // A faster unique method is to use object keys to identify used values, + // but this doesn't work with arrays or objects, which we must also + // consider. See jsperf.com/compare-array-unique-versions/4 for more + // information. + var out = [], + val, + i, + ien = src.length, + j, + k = 0; + + again: for (i = 0; i < ien; i++) { + val = src[i]; + + for (j = 0; j < k; j++) { + if (out[j] === val) { + continue again; + } + } + + out.push(val); + k++; + } - /** - * Attach a sort listener to an element for a given column - * @param {node} nNode the element to attach the sort listener to - * @param {int} iColumn the column that a click on this node will sort on - * @param {function} [fnCallback] callback function when sort is run - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Sort on column 1, when 'sorter' is clicked on - * oTable.fnSortListener( document.getElementById('sorter'), 1 ); - * } ); - */ - this.fnSortListener = function( nNode, iColumn, fnCallback ) - { - this.api( true ).order.listener( nNode, iColumn, fnCallback ); - }; + return out; + }; + + /** + * DataTables utility methods + * + * This namespace provides helper methods that DataTables uses internally to + * create a DataTable, but which are not exclusively used only for DataTables. + * These methods can be used by extension authors to save the duplication of + * code. + * + * @namespace + */ + DataTable.util = { + /** + * Throttle the calls to a function. Arguments and context are maintained + * for the throttled function. + * + * @param {function} fn Function to be called + * @param {integer} freq Call frequency in mS + * @return {function} Wrapped function + */ + throttle: function (fn, freq) { + var frequency = freq !== undefined ? freq : 200, + last, + timer; + return function () { + var that = this, + now = +new Date(), + args = arguments; - /** - * Update a table cell or row - this method will accept either a single value to - * update the cell with, an array of values with one element for each column or - * an object in the same format as the original data source. The function is - * self-referencing in order to make the multi column updates easier. - * @param {object|array|string} mData Data to update the cell/row with - * @param {node|int} mRow TR element you want to update or the aoData index - * @param {int} [iColumn] The column to update, give as null or undefined to - * update a whole row. - * @param {bool} [bRedraw=true] Redraw the table or not - * @param {bool} [bAction=true] Perform pre-draw actions or not - * @returns {int} 0 on success, 1 on error - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell - * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row - * } ); - */ - this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction ) - { - var api = this.api( true ); + if (last && now < last + frequency) { + clearTimeout(timer); - if ( iColumn === undefined || iColumn === null ) { - api.row( mRow ).data( mData ); - } - else { - api.cell( mRow, iColumn ).data( mData ); - } + timer = setTimeout(function () { + last = undefined; + fn.apply(that, args); + }, frequency); + } else { + last = now; + fn.apply(that, args); + } + }; + }, - if ( bAction === undefined || bAction ) { - api.columns.adjust(); - } + /** + * Escape a string such that it can be used in a regular expression + * + * @param {string} val string to escape + * @returns {string} escaped string + */ + escapeRegex: function (val) { + return val.replace(_re_escape_regex, '\\$1'); + }, + }; + + /** + * Create a mapping object that allows camel case parameters to be looked up + * for their Hungarian counterparts. The mapping is stored in a private + * parameter called `_hungarianMap` which can be accessed on the source object. + * @param {object} o + * @memberof DataTable#oApi + */ + function _fnHungarianMap(o) { + var hungarian = 'a aa ai ao as b fn i m o s ', + match, + newKey, + map = {}; + + $.each(o, function (key, val) { + match = key.match(/^([^A-Z]+?)([A-Z])/); + + if (match && hungarian.indexOf(match[1] + ' ') !== -1) { + newKey = key.replace(match[0], match[2].toLowerCase()); + map[newKey] = key; + + if (match[1] === 'o') { + _fnHungarianMap(o[key]); + } + } + }); + + o._hungarianMap = map; + } + + /** + * Convert from camel case parameters to Hungarian, based on a Hungarian map + * created by _fnHungarianMap. + * @param {object} src The model object which holds all parameters that can be + * mapped. + * @param {object} user The object to convert from camel case to Hungarian. + * @param {boolean} force When set to `true`, properties which already have a + * Hungarian value in the `user` object will be overwritten. Otherwise they + * won't be. + * @memberof DataTable#oApi + */ + function _fnCamelToHungarian(src, user, force) { + if (!src._hungarianMap) { + _fnHungarianMap(src); + } - if ( bRedraw === undefined || bRedraw ) { - api.draw(); - } - return 0; - }; + var hungarianKey; + + $.each(user, function (key, val) { + hungarianKey = src._hungarianMap[key]; + + if ( + hungarianKey !== undefined && + (force || user[hungarianKey] === undefined) + ) { + // For objects, we need to buzz down into the object to copy parameters + if (hungarianKey.charAt(0) === 'o') { + // Copy the camelCase options over to the hungarian + if (!user[hungarianKey]) { + user[hungarianKey] = {}; + } + $.extend(true, user[hungarianKey], user[key]); + + _fnCamelToHungarian(src[hungarianKey], user[hungarianKey], force); + } else { + user[hungarianKey] = user[key]; + } + } + }); + } + + /** + * Language compatibility - when certain options are given, and others aren't, we + * need to duplicate the values over, in order to provide backwards compatibility + * with older language files. + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnLanguageCompat(lang) { + var defaults = DataTable.defaults.oLanguage; + var zeroRecords = lang.sZeroRecords; + + /* Backwards compatibility - if there is no sEmptyTable given, then use the same as + * sZeroRecords - assuming that is given. + */ + if ( + !lang.sEmptyTable && + zeroRecords && + defaults.sEmptyTable === 'No data available in table' + ) { + _fnMap(lang, lang, 'sZeroRecords', 'sEmptyTable'); + } + /* Likewise with loading records */ + if ( + !lang.sLoadingRecords && + zeroRecords && + defaults.sLoadingRecords === 'Loading...' + ) { + _fnMap(lang, lang, 'sZeroRecords', 'sLoadingRecords'); + } - /** - * Provide a common method for plug-ins to check the version of DataTables being used, in order - * to ensure compatibility. - * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the - * formats "X" and "X.Y" are also acceptable. - * @returns {boolean} true if this version of DataTables is greater or equal to the required - * version, or false if this version of DataTales is not suitable - * @method - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * alert( oTable.fnVersionCheck( '1.9.0' ) ); - * } ); - */ - this.fnVersionCheck = _ext.fnVersionCheck; + // Old parameter name of the thousands separator mapped onto the new + if (lang.sInfoThousands) { + lang.sThousands = lang.sInfoThousands; + } + var decimal = lang.sDecimal; + if (decimal) { + _addNumericSort(decimal); + } + } + + /** + * Map one parameter onto another + * @param {object} o Object to map + * @param {*} knew The new parameter name + * @param {*} old The old parameter name + */ + var _fnCompatMap = function (o, knew, old) { + if (o[knew] !== undefined) { + o[old] = o[knew]; + } + }; + + /** + * Provide backwards compatibility for the main DT options. Note that the new + * options are mapped onto the old parameters, so this is an external interface + * change only. + * @param {object} init Object to map + */ + function _fnCompatOpts(init) { + _fnCompatMap(init, 'ordering', 'bSort'); + _fnCompatMap(init, 'orderMulti', 'bSortMulti'); + _fnCompatMap(init, 'orderClasses', 'bSortClasses'); + _fnCompatMap(init, 'orderCellsTop', 'bSortCellsTop'); + _fnCompatMap(init, 'order', 'aaSorting'); + _fnCompatMap(init, 'orderFixed', 'aaSortingFixed'); + _fnCompatMap(init, 'paging', 'bPaginate'); + _fnCompatMap(init, 'pagingType', 'sPaginationType'); + _fnCompatMap(init, 'pageLength', 'iDisplayLength'); + _fnCompatMap(init, 'searching', 'bFilter'); + + // Boolean initialisation of x-scrolling + if (typeof init.sScrollX === 'boolean') { + init.sScrollX = init.sScrollX ? '100%' : ''; + } + if (typeof init.scrollX === 'boolean') { + init.scrollX = init.scrollX ? '100%' : ''; + } - var _that = this; - var emptyInit = options === undefined; - var len = this.length; + // Column search objects are in an array, so it needs to be converted + // element by element + var searchCols = init.aoSearchCols; - if ( emptyInit ) { - options = {}; + if (searchCols) { + for (var i = 0, ien = searchCols.length; i < ien; i++) { + if (searchCols[i]) { + _fnCamelToHungarian(DataTable.models.oSearch, searchCols[i]); } + } + } + } + + /** + * Provide backwards compatibility for column options. Note that the new options + * are mapped onto the old parameters, so this is an external interface change + * only. + * @param {object} init Object to map + */ + function _fnCompatCols(init) { + _fnCompatMap(init, 'orderable', 'bSortable'); + _fnCompatMap(init, 'orderData', 'aDataSort'); + _fnCompatMap(init, 'orderSequence', 'asSorting'); + _fnCompatMap(init, 'orderDataType', 'sortDataType'); + + // orderData can be given as an integer + var dataSort = init.aDataSort; + if (dataSort && !$.isArray(dataSort)) { + init.aDataSort = [dataSort]; + } + } + + /** + * Browser feature detection for capabilities, quirks + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnBrowserDetect(settings) { + // We don't need to do this every time DataTables is constructed, the values + // calculated are specific to the browser and OS configuration which we + // don't expect to change between initialisations + if (!DataTable.__browser) { + var browser = {}; + DataTable.__browser = browser; + + // Scrolling feature / quirks detection + var n = $('
') + .css({ + position: 'fixed', + top: 0, + left: 0, + height: 1, + width: 1, + overflow: 'hidden', + }) + .append( + $('
') + .css({ + position: 'absolute', + top: 1, + left: 1, + width: 100, + overflow: 'scroll', + }) + .append( + $('
').css({ + width: '100%', + height: 10, + }) + ) + ) + .appendTo('body'); + + var outer = n.children(); + var inner = outer.children(); + + // Numbers below, in order, are: + // inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth + // + // IE6 XP: 100 100 100 83 + // IE7 Vista: 100 100 100 83 + // IE 8+ Windows: 83 83 100 83 + // Evergreen Windows: 83 83 100 83 + // Evergreen Mac with scrollbars: 85 85 100 85 + // Evergreen Mac without scrollbars: 100 100 100 100 + + // Get scrollbar width + browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth; + + // IE6/7 will oversize a width 100% element inside a scrolling element, to + // include the width of the scrollbar, while other browsers ensure the inner + // element is contained without forcing scrolling + browser.bScrollOversize = + inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100; + + // In rtl text layout, some browsers (most, but not all) will place the + // scrollbar on the left, rather than the right. + browser.bScrollbarLeft = Math.round(inner.offset().left) !== 1; + + // IE8- don't provide height and width for getBoundingClientRect + browser.bBounding = n[0].getBoundingClientRect().width ? true : false; + + n.remove(); + } - this.oApi = this.internal = _ext.internal; + $.extend(settings.oBrowser, DataTable.__browser); + settings.oScroll.iBarWidth = DataTable.__browser.barWidth; + } + + /** + * Array.prototype reduce[Right] method, used for browsers which don't support + * JS 1.6. Done this way to reduce code size, since we iterate either way + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnReduce(that, fn, init, start, end, inc) { + var i = start, + value, + isSet = false; + + if (init !== undefined) { + value = init; + isSet = true; + } - // Extend with old style plug-in API methods - for ( var fn in DataTable.ext.internal ) { - if ( fn ) { - this[fn] = _fnExternApiFunc(fn); - } - } + while (i !== end) { + if (!that.hasOwnProperty(i)) { + continue; + } - this.each(function() { - // For each initialisation we want to give it a clean initialisation - // object that can be bashed around - var o = {}; - var oInit = len > 1 ? // optimisation for single table case - _fnExtend( o, options, true ) : - options; + value = isSet ? fn(value, that[i], i, that) : that[i]; - /*global oInit,_that,emptyInit*/ - var i=0, iLen, j, jLen, k, kLen; - var sId = this.getAttribute( 'id' ); - var bInitHandedOff = false; - var defaults = DataTable.defaults; - var $this = $(this); + isSet = true; + i += inc; + } + return value; + } + + /** + * Add a column to the list used for the table with default values + * @param {object} oSettings dataTables settings object + * @param {node} nTh The th element for this column + * @memberof DataTable#oApi + */ + function _fnAddColumn(oSettings, nTh) { + // Add column to aoColumns array + var oDefaults = DataTable.defaults.column; + var iCol = oSettings.aoColumns.length; + var oCol = $.extend({}, DataTable.models.oColumn, oDefaults, { + nTh: nTh ? nTh : document.createElement('th'), + sTitle: oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '', + aDataSort: oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], + mData: oDefaults.mData ? oDefaults.mData : iCol, + idx: iCol, + }); + oSettings.aoColumns.push(oCol); + + // Add search object for column specific search. Note that the `searchCols[ iCol ]` + // passed into extend can be undefined. This allows the user to give a default + // with only some of the parameters defined, and also not give a default + var searchCols = oSettings.aoPreSearchCols; + searchCols[iCol] = $.extend({}, DataTable.models.oSearch, searchCols[iCol]); + + // Use the default column options function to initialise classes etc + _fnColumnOptions(oSettings, iCol, $(nTh).data()); + } + + /** + * Apply options for a column + * @param {object} oSettings dataTables settings object + * @param {int} iCol column index to consider + * @param {object} oOptions object with sType, bVisible and bSearchable etc + * @memberof DataTable#oApi + */ + function _fnColumnOptions(oSettings, iCol, oOptions) { + var oCol = oSettings.aoColumns[iCol]; + var oClasses = oSettings.oClasses; + var th = $(oCol.nTh); + + // Try to get width information from the DOM. We can't get it from CSS + // as we'd need to parse the CSS stylesheet. `width` option can override + if (!oCol.sWidthOrig) { + // Width attribute + oCol.sWidthOrig = th.attr('width') || null; + + // Style attribute + var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/); + if (t) { + oCol.sWidthOrig = t[1]; + } + } - /* Sanity check */ - if ( this.nodeName.toLowerCase() != 'table' ) - { - _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); - return; - } + /* User specified column options */ + if (oOptions !== undefined && oOptions !== null) { + // Backwards compatibility + _fnCompatCols(oOptions); + + // Map camel case parameters to their Hungarian counterparts + _fnCamelToHungarian(DataTable.defaults.column, oOptions); + + /* Backwards compatibility for mDataProp */ + if (oOptions.mDataProp !== undefined && !oOptions.mData) { + oOptions.mData = oOptions.mDataProp; + } + + if (oOptions.sType) { + oCol._sManualType = oOptions.sType; + } + + // `class` is a reserved word in Javascript, so we need to provide + // the ability to use a valid name for the camel case input + if (oOptions.className && !oOptions.sClass) { + oOptions.sClass = oOptions.className; + } + + $.extend(oCol, oOptions); + _fnMap(oCol, oOptions, 'sWidth', 'sWidthOrig'); + + /* iDataSort to be applied (backwards compatibility), but aDataSort will take + * priority if defined + */ + if (oOptions.iDataSort !== undefined) { + oCol.aDataSort = [oOptions.iDataSort]; + } + _fnMap(oCol, oOptions, 'aDataSort'); + } - /* Backwards compatibility for the defaults */ - _fnCompatOpts( defaults ); - _fnCompatCols( defaults.column ); + /* Cache the data get and set functions for speed */ + var mDataSrc = oCol.mData; + var mData = _fnGetObjectDataFn(mDataSrc); + var mRender = oCol.mRender ? _fnGetObjectDataFn(oCol.mRender) : null; - /* Convert the camel-case defaults to Hungarian */ - _fnCamelToHungarian( defaults, defaults, true ); - _fnCamelToHungarian( defaults.column, defaults.column, true ); + var attrTest = function (src) { + return typeof src === 'string' && src.indexOf('@') !== -1; + }; + oCol._bAttrSrc = + $.isPlainObject(mDataSrc) && + (attrTest(mDataSrc.sort) || + attrTest(mDataSrc.type) || + attrTest(mDataSrc.filter)); + oCol._setter = null; + + oCol.fnGetData = function (rowData, type, meta) { + var innerData = mData(rowData, type, undefined, meta); + + return mRender && type + ? mRender(innerData, type, rowData, meta) + : innerData; + }; + oCol.fnSetData = function (rowData, val, meta) { + return _fnSetObjectDataFn(mDataSrc)(rowData, val, meta); + }; - /* Setting up the initialisation object */ - _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ) ); + // Indicate if DataTables should read DOM data as an object or array + // Used in _fnGetRowElements + if (typeof mDataSrc !== 'number') { + oSettings._rowReadObject = true; + } + /* Feature sorting overrides column specific when off */ + if (!oSettings.oFeatures.bSort) { + oCol.bSortable = false; + th.addClass(oClasses.sSortableNone); // Have to add class here as order event isn't called + } + /* Check that the class assignment is correct for sorting */ + var bAsc = $.inArray('asc', oCol.asSorting) !== -1; + var bDesc = $.inArray('desc', oCol.asSorting) !== -1; + if (!oCol.bSortable || (!bAsc && !bDesc)) { + oCol.sSortingClass = oClasses.sSortableNone; + oCol.sSortingClassJUI = ''; + } else if (bAsc && !bDesc) { + oCol.sSortingClass = oClasses.sSortableAsc; + oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed; + } else if (!bAsc && bDesc) { + oCol.sSortingClass = oClasses.sSortableDesc; + oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed; + } else { + oCol.sSortingClass = oClasses.sSortable; + oCol.sSortingClassJUI = oClasses.sSortJUI; + } + } + + /** + * Adjust the table column widths for new data. Note: you would probably want to + * do a redraw after calling this function! + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnAdjustColumnSizing(settings) { + /* Not interested in doing column width calculation if auto-width is disabled */ + if (settings.oFeatures.bAutoWidth !== false) { + var columns = settings.aoColumns; + + _fnCalculateColumnWidths(settings); + for (var i = 0, iLen = columns.length; i < iLen; i++) { + columns[i].nTh.style.width = columns[i].sWidth; + } + } - /* Check to see if we are re-initialising a table */ - var allSettings = DataTable.settings; - for ( i=0, iLen=allSettings.length ; i