├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── codeception.yml ├── composer.json ├── docs ├── 01-Installation.md └── README.md ├── src ├── Module.php ├── WpnAsset.php ├── assets │ ├── sw.js │ ├── tsc │ ├── web-push.js │ └── web-push.ts ├── commands │ ├── AppController.php │ └── KeysController.php ├── components │ └── Pusher.php ├── controllers │ └── DefaultController.php ├── exceptions │ ├── InvalidApplication.php │ └── SubscriptionNotFound.php ├── migrations │ └── m210828_202754_create_wpn_table.php ├── models │ ├── WpnApp.php │ ├── WpnCampaign.php │ ├── WpnReport.php │ └── WpnSubscription.php └── widgets │ ├── SubscriptionWidget.php │ └── views │ └── subscription.php ├── tests ├── PusherTest.php ├── WebPushMock.php ├── _bootstrap.php ├── _output │ └── .gitignore ├── _support │ ├── .gitkeep │ ├── Helper │ │ └── Unit.php │ ├── UnitTester.php │ └── _generated │ │ └── UnitTesterActions.php ├── config.php └── fixtures │ ├── WpnAppFixture.php │ ├── WpnCampaignFixture.php │ ├── WpnSubscriptionFixture.php │ └── data │ ├── wpn_app.php │ ├── wpn_campaign.php │ └── wpn_subscription.php ├── tsconfig.json └── yii_test /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | tests: 7 | name: PHP ${{ matrix.php }} on ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | os: [ubuntu-latest] 14 | php: ['7.3', '7.4', '8.0'] 15 | 16 | services: 17 | mysql: 18 | image: mysql:5.7 19 | env: 20 | MYSQL_ROOT_PASSWORD: password 21 | MYSQL_DATABASE: wpn_test_db 22 | ports: 23 | - 3306:3306 24 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 25 | 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v2 29 | 30 | - name: Install PHP 31 | uses: shivammathur/setup-php@v2 32 | with: 33 | php-version: ${{ matrix.php }} 34 | tools: pecl 35 | extensions: curl 36 | 37 | - name: Get composer cache directory 38 | id: composer-cache 39 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 40 | 41 | - name: Cache composer dependencies 42 | uses: actions/cache@v1 43 | with: 44 | path: ${{ steps.composer-cache.outputs.dir }} 45 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 46 | restore-keys: ${{ runner.os }}-composer- 47 | 48 | - name: Install dependencies 49 | run: composer update $DEFAULT_COMPOSER_FLAGS 50 | 51 | - name: DB migrations 52 | run: ./yii_test migrate --migrationPath=src/migrations/ --interactive=0 53 | 54 | - name: Run test cases 55 | run: php vendor/bin/codecept run 56 | 57 | 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | composer.lock 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2021 by Clockwork Consulting (https://clockwork.tn) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Yii Software LLC nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

Web Push Notifications for Yii 2

6 |
7 |

8 | 9 | An extension for implementing Web Push Notifications on your website in a breeze. 10 | 11 | Documentation is at [docs/README.md](docs/README.md) 12 | 13 | [![Latest Stable Version](https://poser.pugx.org/machour/yii2-web-push-notifications/v/stable.png)](https://packagist.org/packages/machour/yii2-web-push-notifications) 14 | [![Total Downloads](https://poser.pugx.org/machour/yii2-web-push-notifications/downloads.png)](https://packagist.org/packages/machour/yii2-web-push-notifications) 15 | [![tests](https://github.com/machour/yii2-web-push-notifications/workflows/tests/badge.svg)](https://github.com/machour/yii2-web-push-notifications/actions?query=workflow%3Atests) 16 | 17 | 18 | Installation 19 | ------------ 20 | 21 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 22 | 23 | ``` 24 | php composer.phar require --prefer-dist machour/yii2-web-push-notification 25 | ``` 26 | 27 | Configuration 28 | ------------- 29 | 30 | ### DB 31 | 32 | 33 | This module use the following tables: 34 | 35 | | Name | Role | 36 | |-------------------------|----------------------------------------------------------------------| 37 | | `{{%wpn_app}}` | Represents a Web Push application | 38 | | `{{%wpn_subscription}}` | Represents a Web Push subscriber | 39 | | `{{%wpn_campaign}}` | Represents a Web Push campaign (ie, a push you've scheduled or sent) | 40 | | `{{%wpn_report}}` | Links a subscriber to a push (received ? errored ? ..) | 41 | 42 | Use the following migration to create them: 43 | ```bash 44 | ./yii migrate --migrationPath=vendor/machour/yii2-web-push-notifications/src/migrations/ 45 | ``` 46 | 47 | ### Web 48 | 49 | Add this module to your `\yii\web\Application` config file : 50 | 51 | ```php 52 | return [ 53 | // ... 54 | 'modules' => [ 55 | 'wpn' => [ 56 | 'class' => 'machour\yii2\wpn\Module', 57 | ], 58 | // ... 59 | ], 60 | ]; 61 | ``` 62 | 63 | ### Console 64 | 65 | Add this module to your `\yii\console\Application` config file : 66 | 67 | ```php 68 | return [ 69 | // ... 70 | 'bootstrap' => [..., 'wpn'], 71 | // ... 72 | 'modules' => [ 73 | 'wpn' => [ 74 | 'class' => 'machour\yii2\wpn\Module', 75 | ], 76 | // ... 77 | ], 78 | ] 79 | ``` 80 | 81 | You can now register a new application using the `./yii wpn/app/create` console command. 82 | Arguments are as follow: 83 | 84 | ``` 85 | USAGE 86 | 87 | yii wpn/app/create [...options...] 88 | 89 | - name (required): string 90 | The application name 91 | 92 | - host (required): string 93 | The hostname where the application will be deployed 94 | 95 | - subject (required): string 96 | The contact for the application. Needs to be a URL or a mailto: URL. 97 | This provides a point of contact in case the push service needs to contact you 98 | ``` 99 | -------------------------------------------------------------------------------- /codeception.yml: -------------------------------------------------------------------------------- 1 | namespace: tests 2 | bootstrap: _bootstrap.php 3 | suites: 4 | unit: 5 | path: . 6 | actor: UnitTester 7 | modules: 8 | enabled: 9 | - Yii2: 10 | configFile: 'tests/config.php' 11 | part: [ orm, fixtures ] 12 | # add more modules here 13 | - Asserts 14 | step_decorators: ~ 15 | 16 | settings: 17 | shuffle: true 18 | lint: true 19 | paths: 20 | tests: tests 21 | output: tests/_output 22 | support: tests/_support 23 | data: tests/fixtures/data 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "machour/yii2-web-push-notifications", 3 | "description": "Web Push Notifications for the Yii framework", 4 | "keywords": [ 5 | "yii2", 6 | "push", 7 | "web push" 8 | ], 9 | "type": "yii2-extension", 10 | "license": "BSD-3-Clause", 11 | "authors": [ 12 | { 13 | "name": "Mehdi Achour", 14 | "email": "machour@gmail.com" 15 | } 16 | ], 17 | "minimum-stability": "stable", 18 | "require": { 19 | "php": ">=7.3", 20 | "yiisoft/yii2": "~2.0.13", 21 | "minishlink/web-push": "^6.0", 22 | "matomo/device-detector": "^4.3" 23 | }, 24 | "require-dev": { 25 | "codeception/codeception": "^4.1", 26 | "codeception/module-yii2": "^1.1", 27 | "codeception/module-phpbrowser": "^1.0.0", 28 | "codeception/module-asserts": "^1.0.0", 29 | "codeception/assert-throws": "^1.1" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "machour\\yii2\\wpn\\": "src" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "tests\\": "tests" 39 | } 40 | }, 41 | "repositories": [ 42 | { 43 | "type": "composer", 44 | "url": "https://asset-packagist.org" 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /docs/01-Installation.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machour/yii2-web-push-notifications/f99a4a4e194131410c95057a916ea545967a1eb4/docs/01-Installation.md -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Web Push Notifications for Yii2 2 | 3 | This module provides a complete solution to implement your own Web Push notifications in your Yii2 application. 4 | 5 | You won't need to rely anymore on a service provider like TruePush, OneSignal, .. 6 | 7 | Out of the box, this module allows you to: 8 | 9 | * Quickly set up a Web Push subscription mechanism on your website 10 | * Migrate from your current Web Push provider and own your subscribers 11 | 12 | 13 | ## Summary (TODO) 14 | 15 | * Installation 16 | * Concepts 17 | * Basic Usage 18 | * Migrating from a Push Provider 19 | 20 | ## Credits 21 | 22 | This module is based on the [minishlink/web-push](https://github.com/web-push-libs/web-push-php) library. 23 | 24 | It began as a fork of the [web-push-php-example](https://github.com/Minishlink/web-push-php-example) repository and 25 | turned into a full solution. 26 | 27 | ## Resources 28 | 29 | Check out these links if you want to get a better understanding of the underlying mechanisms in action for Web Push 30 | Notifications : 31 | 32 | * Matthew Gaunt's [Web Push Book](https://web-push-book.gauntface.com/) - a masterpiece. 33 | * This [web-push-php-example](https://github.com/Minishlink/web-push-php-example) - a simple yet efficient demo repository 34 | that you can clone and and run locally to start your journey 35 | * Autopush's [Error codes](https://autopush.readthedocs.io/en/latest/http.html#error-codes) manual section, that documents 36 | all possible error codes when performing a push. -------------------------------------------------------------------------------- /src/Module.php: -------------------------------------------------------------------------------- 1 | controllerNamespace = 'machour\yii2\wpn\commands'; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/WpnAsset.php: -------------------------------------------------------------------------------- 1 | { 2 | console.log("Broadcasting message", payload) 3 | fetch(`/web-push/report`, { 4 | method: 'POST', 5 | body: JSON.stringify(payload) 6 | }).then(() => { 7 | console.log("Action reported"); 8 | } 9 | ).catch(error => { 10 | console.log("Action reporting failed", error); 11 | }); 12 | } 13 | 14 | self.addEventListener("push", function (event) { 15 | console.log("in push event listener", event); 16 | if (!(self.Notification && self.Notification.permission === "granted")) { 17 | console.error("No notification ?"); 18 | return; 19 | } 20 | 21 | const sendNotification = payload => { 22 | console.log("Got payload", payload); 23 | const decoded = JSON.parse(payload); 24 | 25 | const {title, ...options} = decoded; 26 | 27 | return self.registration.showNotification(title, options).then(() => { 28 | reportAction( { 29 | action: "view", 30 | endpoint: event.notification.data.endpoint, 31 | campaignId: event.notification.data.campaignId 32 | }); 33 | }); 34 | }; 35 | 36 | if (event.data) { 37 | const message = event.data.text(); 38 | event.waitUntil(sendNotification(message)); 39 | 40 | } 41 | }); 42 | 43 | self.addEventListener("notificationclick", function (event) { 44 | event.notification.close(); 45 | // This looks to see if the current window is already open and focuses if it is 46 | event.waitUntil( 47 | clients.matchAll({ 48 | type: "window" 49 | }).then(function () { 50 | if (clients.openWindow) { 51 | const data = event.notification.data; // the payload from before 52 | return clients.openWindow(data.url); // open it 53 | } 54 | }).then(() => { 55 | reportAction( { 56 | action: "click", 57 | endpoint: event.notification.data.endpoint, 58 | campaignId: event.notification.data.campaignId 59 | }); 60 | }) 61 | ); 62 | }); 63 | 64 | self.addEventListener("install", function (e) { 65 | e.waitUntil(self.skipWaiting()); 66 | }); 67 | 68 | self.addEventListener("notificationclose", function (e) { 69 | reportAction( { 70 | action: "dismiss", 71 | campaignId: e.notification.data.campaignId, 72 | endpoint: e.notification.data.endpoint, 73 | }); 74 | }); 75 | 76 | self.addEventListener("activate", function (e) { 77 | e.waitUntil(self.clients.claim()); 78 | }); -------------------------------------------------------------------------------- /src/assets/tsc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | tsc --lib es2015,dom web-push.ts 3 | -------------------------------------------------------------------------------- /src/assets/web-push.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __assign = (this && this.__assign) || function () { 3 | __assign = Object.assign || function(t) { 4 | for (var s, i = 1, n = arguments.length; i < n; i++) { 5 | s = arguments[i]; 6 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 7 | t[p] = s[p]; 8 | } 9 | return t; 10 | }; 11 | return __assign.apply(this, arguments); 12 | }; 13 | exports.__esModule = true; 14 | var SubscriptionStatus; 15 | (function (SubscriptionStatus) { 16 | SubscriptionStatus["SUBSCRIBED"] = "subscribed"; 17 | SubscriptionStatus["UNSUBSCRIBED"] = "unsubscribed"; 18 | SubscriptionStatus["UNKNOWN"] = "unknown"; 19 | SubscriptionStatus["BLOCKED"] = "blocked"; 20 | })(SubscriptionStatus || (SubscriptionStatus = {})); 21 | var WebPush = /** @class */ (function () { 22 | function WebPush(appId, publicKey, controller, syncInterval) { 23 | if (controller === void 0) { controller = "/wpn/default"; } 24 | if (syncInterval === void 0) { syncInterval = 1000 * 60 * 10; } 25 | this.LS = { 26 | ENDPOINT: "yii2-wpn-endpoint", 27 | LAST_SYNC: "yii2-wpn-last_sync" 28 | }; 29 | this.appId = appId; 30 | this.publicKey = publicKey; 31 | this.controller = controller; 32 | this.syncInterval = syncInterval; 33 | } 34 | WebPush.prototype.setupRegistration = function (swPath, successCb, failureCb, shouldMigrate) { 35 | var _this = this; 36 | if (swPath === void 0) { swPath = "/sw.js"; } 37 | if (successCb === void 0) { successCb = function (status) { }; } 38 | if (failureCb === void 0) { failureCb = function (error) { }; } 39 | if (shouldMigrate === void 0) { shouldMigrate = function (context) { return false; }; } 40 | if (!("serviceWorker" in navigator)) { 41 | this.log("Service workers are not supported by this browser"); 42 | return false; 43 | } 44 | else if (!("PushManager" in window)) { 45 | this.log("Push notifications are not supported by this browser"); 46 | return false; 47 | } 48 | else if (!("showNotification" in ServiceWorkerRegistration.prototype)) { 49 | this.log("Notifications are not supported by this browser"); 50 | return false; 51 | } 52 | else if (Notification.permission === "denied") { 53 | this.log("Notifications are denied by the user"); 54 | return false; 55 | } 56 | navigator.serviceWorker.register(swPath).then(function () { 57 | var lastSync = parseInt(localStorage.getItem(_this.LS.LAST_SYNC)); 58 | var now = Date.now(); 59 | if (!lastSync || (now - lastSync > _this.syncInterval)) { 60 | console.log(now, lastSync, now - lastSync, _this.syncInterval); 61 | _this.checkSubscription(successCb, failureCb, shouldMigrate); 62 | localStorage.setItem(_this.LS.LAST_SYNC, String(now)); 63 | } 64 | }, function (err) { 65 | _this.log("ServiceWorker registration failed: ", err); 66 | }); 67 | }; 68 | WebPush.prototype.checkSubscription = function (successCb, failureCb, shouldMigrate) { 69 | var _this = this; 70 | if (successCb === void 0) { successCb = function (status) { }; } 71 | if (failureCb === void 0) { failureCb = function (error) { }; } 72 | if (shouldMigrate === void 0) { shouldMigrate = function (context) { return false; }; } 73 | navigator.serviceWorker.ready 74 | .then(function (serviceWorkerRegistration) { 75 | return serviceWorkerRegistration.pushManager.getSubscription(); 76 | }) 77 | .then(function (subscription) { 78 | if (!subscription) { 79 | // We aren't subscribed to push, so set UI to allow the user to enable push 80 | successCb(SubscriptionStatus.UNSUBSCRIBED); 81 | return; 82 | } 83 | // We are subscribed, give the possibility to migrate from an old provider 84 | if (shouldMigrate(_this)) { 85 | return subscription 86 | .unsubscribe() 87 | .then(function () { return navigator.serviceWorker.ready; }) 88 | .then(function (serviceWorkerRegistration) { 89 | return serviceWorkerRegistration.pushManager.subscribe({ 90 | userVisibleOnly: true, 91 | applicationServerKey: _this.urlBase64ToUint8Array(_this.publicKey) 92 | }); 93 | }) 94 | .then(function (subscription) { 95 | return _this.sync(subscription, "POST"); 96 | }); 97 | } 98 | return _this.sync(subscription, "PUT"); 99 | }) 100 | .then(function (subscription) { return subscription && successCb(SubscriptionStatus.SUBSCRIBED); }) // Set your UI to show they have subscribed for push messages 101 | ["catch"](function (e) { 102 | _this.log("Error when updating the subscription", e); 103 | failureCb(e); 104 | }); 105 | }; 106 | WebPush.prototype.subscribe = function (success, failure) { 107 | var _this = this; 108 | if (success === void 0) { success = function (s) { }; } 109 | if (failure === void 0) { failure = function (error) { }; } 110 | return this.checkNotificationPermission() 111 | .then(function () { return navigator.serviceWorker.ready; }) 112 | .then(function (serviceWorkerRegistration) { 113 | return serviceWorkerRegistration.pushManager.subscribe({ 114 | userVisibleOnly: true, 115 | applicationServerKey: _this.urlBase64ToUint8Array(_this.publicKey) 116 | }); 117 | }) 118 | .then(function (subscription) { 119 | // Subscription was successful 120 | // create subscription on your server 121 | localStorage.setItem(_this.LS.ENDPOINT, subscription.endpoint); 122 | return _this.sync(subscription, "POST"); 123 | }) 124 | .then(function (subscription) { return success(subscription); }) // update your UI 125 | ["catch"](function (e) { 126 | if (Notification.permission === "denied") { 127 | // The user denied the notification permission which 128 | // means we failed to subscribe and the user will need 129 | // to manually change the notification permission to 130 | // subscribe to push messages 131 | failure("Notifications are denied by the user."); 132 | } 133 | else { 134 | // A problem occurred with the subscription; common reasons 135 | // include network errors or the user skipped the permission 136 | failure("Impossible to subscribe to push notifications : " + e); 137 | } 138 | }); 139 | }; 140 | WebPush.prototype.unsubscribe = function (success, failure) { 141 | var _this = this; 142 | if (success === void 0) { success = function () { }; } 143 | if (failure === void 0) { failure = function () { }; } 144 | // To unsubscribe from push messaging, you need to get the subscription object 145 | navigator.serviceWorker.ready 146 | .then(function (serviceWorkerRegistration) { 147 | return serviceWorkerRegistration.pushManager.getSubscription(); 148 | }) 149 | .then(function (subscription) { 150 | // Check that we have a subscription to unsubscribe 151 | if (!subscription) { 152 | // No subscription object, so set the state 153 | // to allow the user to subscribe to push 154 | success(); 155 | return; 156 | } 157 | // We have a subscription, unsubscribe 158 | // Remove push subscription from server 159 | return _this.sync(subscription, "DELETE"); 160 | }) 161 | .then(function (subscription) { return subscription.unsubscribe(); }) 162 | .then(function () { 163 | localStorage.removeItem(_this.LS.ENDPOINT); 164 | success(); 165 | })["catch"](function (e) { 166 | // We failed to unsubscribe, this can lead to 167 | // an unusual state, so it may be best to remove 168 | // the users data from your data store and 169 | // inform the user that you have done so 170 | _this.log("Error when unsubscribing the user", e); 171 | failure(); 172 | }); 173 | }; 174 | WebPush.prototype.sync = function (subscription, method) { 175 | var _this = this; 176 | var contentEncoding = (PushManager.supportedContentEncodings || [ 177 | "aesgcm" 178 | ])[0]; 179 | localStorage.setItem(this.LS.ENDPOINT, subscription.endpoint); 180 | return new Promise(function (resolve, reject) { 181 | $.ajax({ 182 | url: _this.controller + "/sync?appId=" + _this.appId, 183 | type: method, 184 | data: JSON.stringify(__assign({ endpoint: subscription.endpoint, publicKey: _this.encode(subscription.getKey("p256dh")), authToken: _this.encode(subscription.getKey("auth")), contentEncoding: contentEncoding }, _this.getCsrfParams())), 185 | success: function (result) { 186 | resolve(subscription); 187 | }, 188 | error: function (err) { 189 | _this.log("WPN Sync error: ", err); 190 | reject(err); 191 | } 192 | }); 193 | }); 194 | }; 195 | WebPush.prototype.checkNotificationPermission = function () { 196 | return new Promise(function (resolve, reject) { 197 | if (Notification.permission === "denied") { 198 | return reject(new Error("Push messages are blocked.")); 199 | } 200 | if (Notification.permission === "granted") { 201 | return resolve(); 202 | } 203 | if (Notification.permission === "default") { 204 | return Notification.requestPermission().then(function (result) { 205 | if (result !== "granted") { 206 | reject(new Error("Bad permission result")); 207 | } 208 | else { 209 | resolve(); 210 | } 211 | }); 212 | } 213 | return reject(new Error("Unknown permission")); 214 | }); 215 | }; 216 | WebPush.prototype.urlBase64ToUint8Array = function (base64String) { 217 | var padding = "=".repeat((4 - (base64String.length % 4)) % 4); 218 | var base64 = (base64String + padding) 219 | .replace(/\-/g, "+") 220 | .replace(/_/g, "/"); 221 | var rawData = window.atob(base64); 222 | var outputArray = new Uint8Array(rawData.length); 223 | for (var i = 0; i < rawData.length; ++i) { 224 | outputArray[i] = rawData.charCodeAt(i); 225 | } 226 | return outputArray; 227 | }; 228 | WebPush.prototype.getCsrfParams = function () { 229 | var _a; 230 | return _a = {}, 231 | _a[jQuery("meta[name=csrf-param]").attr("content")] = jQuery("meta[name=csrf-token]").attr("content"), 232 | _a; 233 | }; 234 | WebPush.prototype.encode = function (str) { 235 | if (!str) { 236 | return null; 237 | } 238 | return btoa(String.fromCharCode.apply(null, new Uint8Array(str))); 239 | }; 240 | WebPush.prototype.log = function () { 241 | var params = []; 242 | for (var _i = 0; _i < arguments.length; _i++) { 243 | params[_i] = arguments[_i]; 244 | } 245 | console.log.apply(console, params); 246 | }; 247 | return WebPush; 248 | }()); 249 | exports.WebPush = WebPush; 250 | -------------------------------------------------------------------------------- /src/assets/web-push.ts: -------------------------------------------------------------------------------- 1 | enum SubscriptionStatus { 2 | SUBSCRIBED = 'subscribed', 3 | UNSUBSCRIBED = 'unsubscribed', 4 | UNKNOWN = 'unknown', 5 | BLOCKED = 'blocked', 6 | } 7 | 8 | export class WebPush { 9 | readonly appId; 10 | readonly publicKey; 11 | readonly controller; 12 | 13 | private readonly LS = { 14 | ENDPOINT: "yii2-wpn-endpoint", 15 | LAST_SYNC: "yii2-wpn-last_sync", 16 | }; 17 | private readonly syncInterval: number; 18 | 19 | constructor( 20 | appId: number, 21 | publicKey: string, 22 | controller = "/wpn/default", 23 | syncInterval = 1000 * 60 * 10 24 | ) { 25 | this.appId = appId; 26 | this.publicKey = publicKey; 27 | this.controller = controller; 28 | this.syncInterval = syncInterval; 29 | } 30 | 31 | setupRegistration(swPath = "/sw.js", successCb = (status: SubscriptionStatus) => {}, failureCb = (error) => {}, shouldMigrate = (context) => false) { 32 | if (!("serviceWorker" in navigator)) { 33 | this.log("Service workers are not supported by this browser"); 34 | return false; 35 | } else if (!("PushManager" in window)) { 36 | this.log("Push notifications are not supported by this browser"); 37 | return false; 38 | } else if (!("showNotification" in ServiceWorkerRegistration.prototype)) { 39 | this.log("Notifications are not supported by this browser"); 40 | return false; 41 | } else if (Notification.permission === "denied") { 42 | this.log("Notifications are denied by the user"); 43 | return false; 44 | } 45 | 46 | navigator.serviceWorker.register(swPath).then( 47 | () => { 48 | const lastSync = parseInt(localStorage.getItem(this.LS.LAST_SYNC)); 49 | const now = Date.now(); 50 | 51 | if (!lastSync || (now - lastSync > this.syncInterval)) { 52 | console.log(now, lastSync, now - lastSync, this.syncInterval) 53 | this.checkSubscription(successCb, failureCb, shouldMigrate); 54 | localStorage.setItem(this.LS.LAST_SYNC, String(now)); 55 | } 56 | }, 57 | (err) => { 58 | this.log("ServiceWorker registration failed: ", err); 59 | } 60 | ); 61 | } 62 | 63 | checkSubscription(successCb = (status: SubscriptionStatus) => {}, failureCb = (error) => {}, shouldMigrate = (context) => false) { 64 | navigator.serviceWorker.ready 65 | .then(serviceWorkerRegistration => 66 | serviceWorkerRegistration.pushManager.getSubscription() 67 | ) 68 | .then(subscription => { 69 | if (!subscription) { 70 | // We aren't subscribed to push, so set UI to allow the user to enable push 71 | successCb(SubscriptionStatus.UNSUBSCRIBED); 72 | return; 73 | } 74 | 75 | // We are subscribed, give the possibility to migrate from an old provider 76 | if (shouldMigrate(this)) { 77 | return subscription 78 | .unsubscribe() 79 | .then(() => navigator.serviceWorker.ready) 80 | .then(serviceWorkerRegistration => 81 | serviceWorkerRegistration.pushManager.subscribe({ 82 | userVisibleOnly: true, 83 | applicationServerKey: this.urlBase64ToUint8Array(this.publicKey) 84 | }) 85 | ) 86 | .then(subscription => { 87 | return this.sync(subscription, "POST"); 88 | }); 89 | } 90 | 91 | return this.sync(subscription, "PUT"); 92 | }) 93 | .then( 94 | subscription => subscription && successCb(SubscriptionStatus.SUBSCRIBED) 95 | ) // Set your UI to show they have subscribed for push messages 96 | .catch(e => { 97 | this.log("Error when updating the subscription", e); 98 | failureCb(e); 99 | }); 100 | } 101 | 102 | subscribe(success = (s: PushSubscription) => {}, failure = (error) => {}) { 103 | return this.checkNotificationPermission() 104 | .then(() => navigator.serviceWorker.ready) 105 | .then(serviceWorkerRegistration => 106 | serviceWorkerRegistration.pushManager.subscribe({ 107 | userVisibleOnly: true, 108 | applicationServerKey: this.urlBase64ToUint8Array(this.publicKey) 109 | }) 110 | ) 111 | .then((subscription: PushSubscription) => { 112 | // Subscription was successful 113 | // create subscription on your server 114 | localStorage.setItem(this.LS.ENDPOINT, subscription.endpoint); 115 | return this.sync(subscription, "POST"); 116 | }) 117 | .then( 118 | subscription => success(subscription) 119 | ) // update your UI 120 | .catch(e => { 121 | if (Notification.permission === "denied") { 122 | // The user denied the notification permission which 123 | // means we failed to subscribe and the user will need 124 | // to manually change the notification permission to 125 | // subscribe to push messages 126 | failure("Notifications are denied by the user."); 127 | } else { 128 | // A problem occurred with the subscription; common reasons 129 | // include network errors or the user skipped the permission 130 | failure("Impossible to subscribe to push notifications : " + e); 131 | } 132 | }); 133 | } 134 | 135 | unsubscribe(success = () => {}, failure = () => {}) { 136 | // To unsubscribe from push messaging, you need to get the subscription object 137 | navigator.serviceWorker.ready 138 | .then(serviceWorkerRegistration => 139 | serviceWorkerRegistration.pushManager.getSubscription() 140 | ) 141 | .then(subscription => { 142 | // Check that we have a subscription to unsubscribe 143 | if (!subscription) { 144 | // No subscription object, so set the state 145 | // to allow the user to subscribe to push 146 | success(); 147 | return; 148 | } 149 | 150 | // We have a subscription, unsubscribe 151 | // Remove push subscription from server 152 | return this.sync(subscription, "DELETE"); 153 | }) 154 | .then(subscription => subscription.unsubscribe()) 155 | .then(() => { 156 | localStorage.removeItem(this.LS.ENDPOINT); 157 | success(); 158 | }) 159 | .catch(e => { 160 | // We failed to unsubscribe, this can lead to 161 | // an unusual state, so it may be best to remove 162 | // the users data from your data store and 163 | // inform the user that you have done so 164 | this.log("Error when unsubscribing the user", e); 165 | failure(); 166 | }); 167 | } 168 | 169 | sync(subscription, method): Promise { 170 | const contentEncoding = (PushManager.supportedContentEncodings || [ 171 | "aesgcm" 172 | ])[0]; 173 | 174 | localStorage.setItem(this.LS.ENDPOINT, subscription.endpoint); 175 | return new Promise((resolve, reject) => { 176 | $.ajax({ 177 | url: `${this.controller}/sync?appId=${this.appId}`, 178 | type: method, 179 | data: JSON.stringify({ 180 | endpoint: subscription.endpoint, 181 | publicKey: this.encode(subscription.getKey("p256dh")), 182 | authToken: this.encode(subscription.getKey("auth")), 183 | contentEncoding, 184 | ...this.getCsrfParams() 185 | }), 186 | success: result => { 187 | resolve(subscription); 188 | }, 189 | error: err => { 190 | this.log("WPN Sync error: ", err); 191 | reject(err); 192 | } 193 | }); 194 | }); 195 | } 196 | 197 | checkNotificationPermission() { 198 | return new Promise((resolve, reject) => { 199 | if (Notification.permission === "denied") { 200 | return reject(new Error("Push messages are blocked.")); 201 | } 202 | 203 | if (Notification.permission === "granted") { 204 | return resolve(); 205 | } 206 | 207 | if (Notification.permission === "default") { 208 | return Notification.requestPermission().then(result => { 209 | if (result !== "granted") { 210 | reject(new Error("Bad permission result")); 211 | } else { 212 | resolve(); 213 | } 214 | }); 215 | } 216 | 217 | return reject(new Error("Unknown permission")); 218 | }); 219 | } 220 | 221 | urlBase64ToUint8Array(base64String) { 222 | const padding = "=".repeat((4 - (base64String.length % 4)) % 4); 223 | const base64 = (base64String + padding) 224 | .replace(/\-/g, "+") 225 | .replace(/_/g, "/"); 226 | 227 | const rawData = window.atob(base64); 228 | const outputArray = new Uint8Array(rawData.length); 229 | 230 | for (let i = 0; i < rawData.length; ++i) { 231 | outputArray[i] = rawData.charCodeAt(i); 232 | } 233 | return outputArray; 234 | } 235 | 236 | getCsrfParams() { 237 | return { 238 | [jQuery("meta[name=csrf-param]").attr("content")]: jQuery( 239 | "meta[name=csrf-token]" 240 | ).attr("content") 241 | }; 242 | } 243 | 244 | encode(str) { 245 | if (!str) { 246 | return null; 247 | } 248 | 249 | return btoa(String.fromCharCode.apply(null, new Uint8Array(str))); 250 | } 251 | 252 | log(...params) { 253 | console.log(...params); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/commands/AppController.php: -------------------------------------------------------------------------------- 1 | all(); 16 | 17 | $rows = []; 18 | 19 | foreach ($apps as $app) { 20 | $rows[] = [$app->id, $app->name, $app->host, $app->subject, $app->enabled ? $this->ansiFormat('✓', Console::FG_GREEN) : $this->ansiFormat('x', Console::FG_RED)]; 21 | } 22 | 23 | echo Table::widget([ 24 | 'headers' => ['ID', 'Name', 'Host', 'Subject', 'Enabled'], 25 | 'rows' => $rows 26 | ]); 27 | } 28 | 29 | /** 30 | * @param string $name The application name 31 | * @param string $host The hostname where the application will be deployed 32 | * @param string $subject The contact for the application. Needs to be a URL or a mailto: URL. 33 | * This provides a point of contact in case the push service needs to contact you 34 | */ 35 | public function actionCreate($name, $host, $subject) 36 | { 37 | try { 38 | $keys = VAPID::createVapidKeys(); 39 | $app = new WpnApp([ 40 | 'name' => $name, 41 | 'host' => $host, 42 | 'subject' => $subject, 43 | 'private_key' => $keys['privateKey'], 44 | 'public_key' => $keys['publicKey'], 45 | 'enabled' => true, 46 | ]); 47 | 48 | if ($app->save()) { 49 | $this->stdout("Application #{$app->id} created and enabled.\n", Console::FG_GREEN); 50 | } else { 51 | $this->stdout("Application could not be created. Please fix the following errors:\n\n", Console::FG_RED); 52 | foreach ($app->errors as $attribute => $errors) { 53 | echo ' - ' . $this->ansiFormat($attribute, Console::FG_CYAN) . ': ' . implode('; ', $errors) . "\n"; 54 | } 55 | } 56 | } catch (\ErrorException $e) { 57 | $this->stdout("Could not generate VapId keys: {$e->getMessage()}\n", Console::FG_RED); 58 | } 59 | 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/commands/KeysController.php: -------------------------------------------------------------------------------- 1 | wp = $wp; 24 | parent::__construct($config); 25 | } 26 | 27 | /** 28 | * @throws \yii\db\Exception 29 | * @throws \ErrorException 30 | * @throws InvalidApplication 31 | */ 32 | public function sendPush(WpnCampaign $campaign) 33 | { 34 | if (!$campaign->app->enabled) { 35 | throw new InvalidApplication(); 36 | } 37 | 38 | $auth = [ 39 | 'VAPID' => [ 40 | 'subject' => $campaign->app->subject, 41 | 'publicKey' => $campaign->app->public_key, 42 | 'privateKey' => $campaign->app->private_key, 43 | ], 44 | ]; 45 | 46 | 47 | 48 | $query = WpnSubscription::find() 49 | ->where(['subscribed' => true, 'app_id' => $campaign->app_id]) 50 | ->orderBy(['last_seen' => SORT_DESC]); 51 | 52 | if ($campaign->test_only) { 53 | $query->andWhere(['test_user' => 1]); 54 | } 55 | 56 | /** @var WpnSubscription[] $subscriptions */ 57 | $subscriptions = ArrayHelper::index($query->all(), 'endpoint'); 58 | 59 | foreach ($subscriptions as $subscription) { 60 | try { 61 | $options = $campaign->options; 62 | $options['data']['endpoint'] = $subscription->endpoint; 63 | 64 | $this->wp->queueNotification($subscription, Json::encode($options), [], $auth); 65 | } catch (\Exception $e) { 66 | $subscription->last_error = $e->getMessage(); 67 | $subscription->save(); 68 | } 69 | } 70 | 71 | $unsubscribedIds = []; 72 | 73 | $reports = []; 74 | 75 | foreach ($this->wp->flush() as $report) { 76 | $endpoint = $report->getRequest()->getUri()->__toString(); 77 | $subscription = $subscriptions[$endpoint]; 78 | $spParams = [ 79 | 'subscription_id' => $subscription->id, 80 | 'campaign_id' => $campaign->id, 81 | 'sent_at' => date('Y-m-d H:i:s'), 82 | 'received' => true, 83 | ]; 84 | 85 | if (!$report->isSuccess()) { 86 | $spParams['received'] = false; 87 | $response = $report->getResponse(); 88 | if ($report->isSubscriptionExpired()) { 89 | $unsubscribedIds[] = $subscription->id; 90 | } else if ($response->getStatusCode() === 301) { 91 | $newEndpoint = $response->getHeaderLine('Location'); 92 | if ($newEndpoint) { 93 | $subscription->endpoint = $newEndpoint; 94 | $subscription->save(); 95 | } else { 96 | $unsubscribedIds[] = $subscription->id; 97 | } 98 | } 99 | } 100 | 101 | $reports[] = $spParams; 102 | 103 | } 104 | 105 | if (count($unsubscribedIds)) { 106 | WpnSubscription::updateAll([ 107 | 'subscribed' => false, 108 | 'reason' => 'last push failed' 109 | ], ['id' => $unsubscribedIds]); 110 | } 111 | 112 | if (count($reports)) { 113 | Yii::$app->db->createCommand() 114 | ->batchInsert( 115 | '{{%wpn_report}}', array_keys($reports[0]), $reports) 116 | ->execute(); 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /src/controllers/DefaultController.php: -------------------------------------------------------------------------------- 1 | id == 'report') { 29 | $this->enableCsrfValidation = false; 30 | } 31 | 32 | return parent::beforeAction($action); 33 | } 34 | 35 | public function behaviors(): array 36 | { 37 | return [ 38 | 'verbs' => [ 39 | 'class' => VerbFilter::class, 40 | 'actions' => [ 41 | 'sync' => ['post', 'put', 'delete'], 42 | 'report' => ['post'], 43 | 'push' => ['get'], 44 | ], 45 | ], 46 | ]; 47 | } 48 | 49 | /** 50 | * @throws SubscriptionNotFound 51 | * @throws Exception 52 | */ 53 | public function actionSync($appId): \yii\web\Response 54 | { 55 | $request = Yii::$app->request; 56 | $data = Json::decode($request->rawBody); 57 | 58 | if (!isset($data['endpoint'])) { 59 | return $this->asJson([ 60 | 'success' => false, 61 | 'message' => 'Not a subscription', 62 | ]); 63 | } 64 | 65 | switch ($request->method) { 66 | case 'POST': 67 | 68 | $subscription = new WpnSubscription([ 69 | 'app_id' => $appId, 70 | 'endpoint' => $data['endpoint'], 71 | 'auth' => $data['authToken'], 72 | 'public_key' => $data['publicKey'], 73 | 'content_encoding' => $data['contentEncoding'], 74 | 'subscribed' => true, 75 | 'yii_user_id' => Yii::$app->user->id, 76 | 'ua' => $request->userAgent, 77 | 'ip' => $request->remoteIP, 78 | 'test_user' => false, 79 | 'last_seen' => new Expression('NOW()'), 80 | ]); 81 | 82 | $dd = new DeviceDetector($subscription->ua); 83 | $dd->parse(); 84 | $subscription->os = $dd->getOs('name'); 85 | $subscription->browser = $dd->getClient('name'); 86 | 87 | if ($subscription->save()) { 88 | return $this->asJson(['success' => true, 'user_id' => $subscription->id]); 89 | } else { 90 | return $this->asJson(['success' => false, 'message' => var_export($subscription->errors, 1)]); 91 | } 92 | 93 | case 'PUT': 94 | $subscription = WpnSubscription::findOne(['endpoint' => $data['endpoint']]); 95 | if ($subscription) { 96 | $subscription->setAttributes([ 97 | 'subscribed' => true, 98 | 'auth' => $data['authToken'], 99 | 'public_key' => $data['publicKey'], 100 | 'content_encoding' => $data['contentEncoding'], 101 | 'ua' => $request->userAgent, 102 | 'ip' => $request->remoteIP, 103 | 'last_seen' => new Expression('NOW()'), 104 | ]); 105 | if (!Yii::$app->user->isGuest) { 106 | $subscription->yii_user_id = Yii::$app->user->id; 107 | } 108 | 109 | if ($subscription->save()) { 110 | return $this->asJson(['success' => true, 'user_id' => $subscription->id]); 111 | } 112 | 113 | return $this->asJson(['success' => false, 'message' => var_export($subscription->errors, 1)]); 114 | } 115 | 116 | throw new SubscriptionNotFound(); 117 | 118 | case 'DELETE': 119 | $subscription = WpnSubscription::findOne(['endpoint' => $data['endpoint'], 'subscribed' => true]); 120 | if ($subscription) { 121 | $subscription->subscribed = false; 122 | $subscription->reason = 'user request'; 123 | $subscription->save(); 124 | return $this->asJson(['success' => true, 'user_id' => $subscription->id]); 125 | } 126 | 127 | return $this->asJson(['success' => false, 'message' => 'Subscription not found']); 128 | } 129 | 130 | } 131 | 132 | public function actionReport(): \yii\web\Response 133 | { 134 | $request = Yii::$app->request; 135 | $data = Json::decode($request->rawBody); 136 | 137 | $campaign_id = $data['campaignId']; 138 | $endpoint = $data['endpoint']; 139 | $action = $data['action']; 140 | 141 | $campaign = WpnCampaign::findOne($campaign_id); 142 | if (!$campaign) { 143 | throw new InvalidArgumentException("Campaign not found"); 144 | } 145 | 146 | $subscription = WpnSubscription::findOne(['endpoint' => $endpoint, 'app_id' => $campaign->app_id]); 147 | if (!$subscription) { 148 | throw new InvalidArgumentException("Subscription not found"); 149 | } 150 | 151 | $sp = WpnReport::findOne(['campaign_id' => $campaign_id, 'subscription_id' => $subscription->id]); 152 | if (!$sp) { 153 | throw new InvalidArgumentException("Push not sent to this user"); 154 | } 155 | 156 | switch ($action) { 157 | case 'dismiss': 158 | $sp->dismissed = true; 159 | break; 160 | 161 | case 'click': 162 | $sp->clicked = true; 163 | break; 164 | } 165 | 166 | return $this->asJson([ 167 | $campaign_id, $endpoint, $action, $sp->save() 168 | ]); 169 | } 170 | 171 | public function actionServiceWorker(): string 172 | { 173 | Yii::$app->response->format = Response::FORMAT_RAW; 174 | Yii::$app->response->headers->add('Content-Type', 'application/javascript'); 175 | 176 | return file_get_contents(__DIR__ . '/../assets/sw.js'); 177 | } 178 | 179 | public function actionPush($id) 180 | { 181 | $campaign = WpnCampaign::findOne($id); 182 | if (!$campaign) { 183 | throw new NotFoundHttpException("Unknown Campaign"); 184 | } 185 | 186 | /** @var Pusher $pusher */ 187 | $pusher = $this->module->get('pusher'); 188 | 189 | $pusher->sendPush($campaign); 190 | } 191 | } -------------------------------------------------------------------------------- /src/exceptions/InvalidApplication.php: -------------------------------------------------------------------------------- 1 | createTable('{{%wpn_app}}', [ 16 | 'id' => $this->primaryKey(), 17 | 'name' => $this->string()->notNull(), 18 | 'host' => $this->string(180)->unique()->notNull(), 19 | 'private_key' => $this->string(50)->notNull(), 20 | 'public_key' => $this->string(100)->notNull(), 21 | 'subject' => $this->string(255)->notNull(), 22 | 'enabled' => $this->boolean()->notNull(), 23 | 'icon' => $this->string()->null(), 24 | 'badge' => $this->string()->null(), 25 | 'created_at' => $this->dateTime()->notNull(), 26 | 'updated_at' => $this->dateTime()->notNull(), 27 | ], 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'); 28 | 29 | $this->createTable('{{%wpn_subscription}}', [ 30 | 'id' => $this->primaryKey(), 31 | 'endpoint' => $this->string(255)->notNull(), 32 | 'auth' => $this->string()->notNull(), 33 | 'public_key' => $this->string()->notNull(), 34 | 'content_encoding' => $this->string()->notNull(), 35 | 'subscribed' => $this->boolean()->notNull(), 36 | 'test_user' => $this->boolean(), 37 | 'yii_user_id' => $this->integer(), 38 | 'app_id' => $this->integer()->notNull(), 39 | 'ua' => $this->string(), 40 | 'ip' => $this->string()->notNull(), 41 | 'os' => $this->string(), 42 | 'browser' => $this->string(), 43 | 'last_seen' => $this->dateTime(), 44 | 'last_error' => $this->string(), 45 | 'reason' => $this->string(), 46 | 'created_at' => $this->dateTime()->notNull(), 47 | 'updated_at' => $this->dateTime()->notNull(), 48 | ], 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'); 49 | $this->createIndex('uq_wpn_subscription_endpoint', '{{%wpn_subscription}}', 'endpoint', true); 50 | $this->createIndex('uq_wpn_subscription_yii_user_id', '{{%wpn_subscription}}', 'yii_user_id'); 51 | $this->createIndex('uq_wpn_subscription_subscribed', '{{%wpn_subscription}}', 'subscribed'); 52 | $this->createIndex('uq_wpn_subscription_app_id', '{{%wpn_subscription}}', 'app_id'); 53 | 54 | $this->addForeignKey('fk_wpn_subscription_app', '{{%wpn_subscription}}', 'app_id', '{{%wpn_app}}', 'id'); 55 | 56 | $this->createTable('{{%wpn_campaign}}', [ 57 | 'id' => $this->primaryKey(), 58 | 'app_id' => $this->integer()->notNull(), 59 | 'title' => $this->string()->notNull(), 60 | 'tag' => $this->string(180)->unique()->notNull(), 61 | 'body' => $this->string()->notNull(), 62 | 'url' => $this->string(), 63 | 'image' => $this->string(), 64 | 'test_only' => $this->boolean(), 65 | 'created_at' => $this->dateTime()->notNull(), 66 | 'scheduled_at' => $this->dateTime()->notNull(), 67 | 'started_at' => $this->dateTime(), 68 | 'finished_at' => $this->dateTime(), 69 | 'updated_at' => $this->dateTime()->notNull(), 70 | 'extra' => $this->text(), 71 | ]); 72 | $this->createIndex('uq_wpn_campaign_app_id', '{{%wpn_campaign}}', 'app_id'); 73 | $this->addForeignKey('fk_wpn_campaign_app', '{{%wpn_campaign}}', 'app_id', '{{%wpn_app}}', 'id'); 74 | 75 | $this->createTable('{{%wpn_report}}', [ 76 | 'id' => $this->primaryKey(), 77 | 'campaign_id' => $this->integer()->notNull(), 78 | 'subscription_id' => $this->integer()->notNull(), 79 | 'sent_at' => $this->dateTime()->notNull(), 80 | 'received' => $this->boolean(), 81 | 'viewed' => $this->boolean(), 82 | 'clicked' => $this->boolean(), 83 | 'dismissed' => $this->boolean(), 84 | ]); 85 | $this->createIndex('uq_wpn_report', '{{%wpn_report}}', ['subscription_id', 'campaign_id'], true); 86 | 87 | $this->createIndex('idx_wpn_report_campaign', '{{%wpn_report}}','campaign_id'); 88 | $this->addForeignKey('fk_wpn_report_campaign', '{{%wpn_report}}','campaign_id', '{{%wpn_campaign}}', 'id'); 89 | 90 | $this->createIndex('idx_wpn_report_subscription', '{{%wpn_report}}','subscription_id'); 91 | $this->addForeignKey('fk_wpn_report_subscription', '{{%wpn_report}}','subscription_id', '{{%wpn_subscription}}', 'id'); 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function safeDown() 98 | { 99 | $this->dropTable('{{%wpn_report}}'); 100 | $this->dropTable('{{%wpn_campaign}}'); 101 | $this->dropTable('{{%wpn_subscription}}'); 102 | $this->dropTable('{{%wpn_app}}'); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/models/WpnApp.php: -------------------------------------------------------------------------------- 1 | TimestampBehavior::class, 39 | 'attributes' => [ 40 | self::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'], 41 | self::EVENT_BEFORE_UPDATE => ['updated_at'], 42 | ], 43 | 'value' => new Expression('NOW()'), 44 | ], 45 | ]; 46 | } 47 | 48 | public function rules(): array 49 | { 50 | return [ 51 | [['name', 'host', 'enabled', 'public_key', 'private_key', 'subject'], 'required'], 52 | [['name', 'subject', 'icon', 'badge'], 'string', 'max' => 255], 53 | [['host'], 'string', 'max' => 180], 54 | [['private_key'], 'string', 'max' => 50], 55 | [['public_key'], 'string', 'max' => 100], 56 | [['enabled'], 'boolean'], 57 | [['host'], 'unique'], 58 | ]; 59 | } 60 | 61 | public function getCampaigns(): \yii\db\ActiveQuery 62 | { 63 | return $this->hasMany(WpnCampaign::class, ['app_id' => 'id']); 64 | } 65 | 66 | public function getSubscriptions(): \yii\db\ActiveQuery 67 | { 68 | return $this->hasMany(WpnSubscription::class, ['app_id' => 'id']); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/models/WpnCampaign.php: -------------------------------------------------------------------------------- 1 | TimestampBehavior::class, 45 | 'attributes' => [ 46 | self::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'], 47 | self::EVENT_BEFORE_UPDATE => ['updated_at'], 48 | ], 49 | 'value' => new Expression('NOW()'), 50 | ], 51 | ]; 52 | } 53 | 54 | public function rules(): array 55 | { 56 | return [ 57 | [['title', 'body', 'scheduled_at', 'tag', 'app_id'], 'required'], 58 | [['app_id'], 'integer'], 59 | [['scheduled_at', 'started_at', 'finished_at'], 'datetime', 'format' => 'php:Y-m-d H:i:s'], 60 | [['extra'], 'string'], 61 | [['test_only'], 'boolean'], 62 | [['title', 'body', 'url', 'image', 'tag'], 'string', 'max' => 255], 63 | [['app_id'], 'exist', 'skipOnError' => true, 'targetClass' => WpnApp::class, 'targetAttribute' => ['app_id' => 'id']], 64 | ]; 65 | } 66 | 67 | public function getReports(): \yii\db\ActiveQuery 68 | { 69 | return $this->hasMany(WpnReport::class, ['campaign_id' => 'id']); 70 | } 71 | 72 | public function getSubscriptions(): \yii\db\ActiveQuery 73 | { 74 | return $this->hasMany(WpnSubscription::class, ['id' => 'subscription_id'])->via('reports'); 75 | } 76 | 77 | public function getApp(): \yii\db\ActiveQuery 78 | { 79 | return $this->hasOne(WpnApp::class, ['id' => 'app_id']); 80 | } 81 | 82 | public function getOptions(): array 83 | { 84 | $options = [ 85 | 'title' => $this->title, 86 | 'body' => $this->body, 87 | 'tag' => $this->tag, 88 | 'data' => [ 89 | 'url' => $this->url, 90 | 'campaignId' => $this->id, 91 | ], 92 | ]; 93 | 94 | if ($this->image) { 95 | $options['image'] = $this->image; 96 | } 97 | 98 | if ($this->app->badge) { 99 | $options['badge'] = $this->app->badge; 100 | } 101 | 102 | if ($this->app->icon) { 103 | $options['icon'] = $this->app->icon; 104 | } 105 | 106 | return $options; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/models/WpnReport.php: -------------------------------------------------------------------------------- 1 | 'php:Y-m-d H:i:s'], 36 | [['received', 'viewed', 'clicked', 'dismissed'], 'boolean'], 37 | [['subscription_id', 'campaign_id'], 'unique', 'targetAttribute' => ['subscription_id', 'campaign_id']], 38 | [['campaign_id'], 'exist', 'skipOnError' => true, 'targetClass' => WpnCampaign::class, 'targetAttribute' => ['campaign_id' => 'id']], 39 | [['subscription_id'], 'exist', 'skipOnError' => true, 'targetClass' => WpnSubscription::class, 'targetAttribute' => ['subscription_id' => 'id']], 40 | ]; 41 | } 42 | 43 | public function getCampaign(): \yii\db\ActiveQuery 44 | { 45 | return $this->hasOne(WpnCampaign::class, ['id' => 'campaign_id']); 46 | } 47 | 48 | public function getSubscription(): \yii\db\ActiveQuery 49 | { 50 | return $this->hasOne(WpnSubscription::class, ['id' => 'subscription_id']); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/models/WpnSubscription.php: -------------------------------------------------------------------------------- 1 | TimestampBehavior::class, 49 | 'attributes' => [ 50 | self::EVENT_BEFORE_INSERT => ['created_at', 'last_seen', 'updated_at'], 51 | self::EVENT_BEFORE_UPDATE => ['updated_at', 'last_seen'], 52 | ], 53 | 'value' => new Expression('NOW()'), 54 | ], 55 | ]; 56 | } 57 | 58 | public function rules(): array 59 | { 60 | return [ 61 | [['endpoint', 'auth', 'public_key', 'content_encoding', 'subscribed', 'ip', 'app_id'], 'required'], 62 | [['yii_user_id', 'app_id'], 'integer'], 63 | [['endpoint', 'auth', 'public_key', 'content_encoding', 'ua', 'ip', 'os', 'browser', 'last_error', 'reason'], 'string', 'max' => 255], 64 | [['subscribed', 'test_user'], 'boolean'], 65 | [['endpoint'], 'unique'], 66 | [['app_id'], 'exist', 'skipOnError' => true, 'targetClass' => WpnApp::class, 'targetAttribute' => ['app_id' => 'id']], 67 | ]; 68 | } 69 | 70 | public function getReports(): \yii\db\ActiveQuery 71 | { 72 | return $this->hasMany(WpnReport::class, ['subscription_id' => 'id']); 73 | } 74 | 75 | public function getCampaigns(): \yii\db\ActiveQuery 76 | { 77 | return $this->hasMany(WpnCampaign::class, ['id' => 'campaign_id'])->via('reports'); 78 | } 79 | 80 | public function getApp(): \yii\db\ActiveQuery 81 | { 82 | return $this->hasOne(WpnApp::class, ['id' => 'app_id']); 83 | } 84 | 85 | public function setEndpoint($value) 86 | { 87 | $this->endpoint = $value; 88 | } 89 | 90 | public function getEndpoint(): string 91 | { 92 | return $this->endpoint; 93 | } 94 | 95 | public function getPublicKey(): ?string 96 | { 97 | return $this->public_key; 98 | } 99 | 100 | public function getAuthToken(): ?string 101 | { 102 | return $this->auth; 103 | } 104 | 105 | public function getContentEncoding(): ?string 106 | { 107 | return $this->content_encoding; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/widgets/SubscriptionWidget.php: -------------------------------------------------------------------------------- 1 | app || !is_a($this->app, WpnApp::class)) { 23 | throw new InvalidConfigException('A valid WpnApp must be provided in the $app property'); 24 | } 25 | parent::init(); 26 | } 27 | 28 | public function run(): string 29 | { 30 | if (!$this->app->enabled) { 31 | return ''; 32 | } 33 | 34 | return $this->render('subscription', [ 35 | 'app' => $this->app, 36 | 'shouldMigrate' => $this->shouldMigrate, 37 | ]); 38 | } 39 | } -------------------------------------------------------------------------------- /src/widgets/views/subscription.php: -------------------------------------------------------------------------------- 1 | registerJs(<< { 13 | var wp = new WebPush({$app->id}, "{$app->public_key}"); 14 | var wpnStatus = jQuery('.wpn-status'); 15 | jQuery(".wpn-subscribe").on("click", function() { 16 | wp.subscribe(function(subscription) { 17 | wpnStatus.text("Subscribed, endpoint is" + subscription.endpoint); 18 | }, function (error) { 19 | wpnStatus.text("Got error: " + error); 20 | }); 21 | }); 22 | 23 | jQuery(".wpn-unsubscribe").on("click", function() { 24 | wp.unsubscribe(function () { 25 | wpnStatus.text("Unsubscribed"); 26 | }, function () { 27 | wpnStatus.text("Error while unsubscribing"); 28 | }); 29 | }); 30 | 31 | wp.setupRegistration("/sw.js", function(status) { 32 | wpnStatus.text(status); 33 | }, function(error) { 34 | wpnStatus.text("error"); 35 | console.error(error); 36 | }, $shouldMigrate); 37 | 38 | 39 | }); 40 | JS 41 | ); 42 | 43 | ?> 44 | 45 |
46 |
unknown
47 | 48 | 49 |
50 | -------------------------------------------------------------------------------- /tests/PusherTest.php: -------------------------------------------------------------------------------- 1 | tester->haveFixtures([ 27 | 'app' => [ 28 | 'class' => WpnAppFixture::class, 29 | ], 30 | 'campaign' => [ 31 | 'class' => WpnCampaignFixture::class, 32 | ], 33 | 'subscription' => [ 34 | 'class' => WpnSubscriptionFixture::class, 35 | ], 36 | ]); 37 | 38 | $stub = $this->make(WebPush::class, [ 39 | 'queueNotification' => function() { 40 | 41 | }, 42 | 'flush' => function() { 43 | return [1]; 44 | } 45 | ]); 46 | \Yii::$container->set(WebPush::class, $stub); 47 | 48 | 49 | $module = Module::getInstance(); 50 | $this->pusher = $module->get('pusher'); 51 | 52 | } 53 | 54 | // tests 55 | public function testRefusingDisabledApplication() 56 | { 57 | $campaign = $this->tester->grabFixture('campaign', 1); 58 | $this->assertThrows(InvalidApplication::class, function() use ($campaign) { 59 | $this->pusher->sendPush($campaign); 60 | }); 61 | } 62 | 63 | 64 | public function testMock() 65 | { 66 | $campaign = $this->tester->grabFixture('campaign', 0); 67 | 68 | $this->pusher->sendPush($campaign); 69 | } 70 | } -------------------------------------------------------------------------------- /tests/WebPushMock.php: -------------------------------------------------------------------------------- 1 | notifications[] = new Notification($subscription, $payload, $options, $auth); 19 | } 20 | 21 | public function flush(?int $batchSize = null): \Generator 22 | { 23 | if (null === $this->notifications || empty($this->notifications)) { 24 | yield from []; 25 | return; 26 | } 27 | 28 | if (null === $batchSize) { 29 | $batchSize = $this->defaultOptions['batchSize']; 30 | } 31 | 32 | $batches = array_chunk($this->notifications, $batchSize); 33 | 34 | // reset queue 35 | $this->notifications = []; 36 | 37 | foreach ($batches as $batch) { 38 | // for each endpoint server type 39 | $requests = $this->prepare($batch); 40 | 41 | $promises = []; 42 | 43 | foreach ($requests as $request) { 44 | $promises[] = $this->client->sendAsync($request) 45 | ->then(function ($response) use ($request) { 46 | /** @var ResponseInterface $response * */ 47 | return new MessageSentReport($request, $response); 48 | }) 49 | ->otherwise(function ($reason) { 50 | /** @var RequestException $reason **/ 51 | if (method_exists($reason, 'getResponse')) { 52 | $response = $reason->getResponse(); 53 | } else { 54 | $response = null; 55 | } 56 | return new MessageSentReport($reason->getRequest(), $response, false, $reason->getMessage()); 57 | }); 58 | } 59 | 60 | foreach ($promises as $promise) { 61 | yield $promise->wait(); 62 | } 63 | } 64 | 65 | if ($this->reuseVAPIDHeaders) { 66 | $this->vapidHeaders = []; 67 | } 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | haveFixtures([ 25 | * 'posts' => PostsFixture::className(), 26 | * 'user' => [ 27 | * 'class' => UserFixture::className(), 28 | * 'dataFile' => '@tests/_data/models/user.php', 29 | * ], 30 | * ]); 31 | * ``` 32 | * 33 | * Note: if you need to load fixtures before a test (probably before the 34 | * cleanup transaction is started; `cleanup` option is `true` by default), 35 | * you can specify the fixtures in the `_fixtures()` method of a test case 36 | * 37 | * ```php 38 | * [ 43 | * 'class' => UserFixture::className(), 44 | * 'dataFile' => codecept_data_dir() . 'user.php' 45 | * ] 46 | * ]; 47 | * } 48 | * ``` 49 | * instead of calling `haveFixtures` in Cest `_before` 50 | * 51 | * @param $fixtures 52 | * @part fixtures 53 | * @see \Codeception\Module\Yii2::haveFixtures() 54 | */ 55 | public function haveFixtures($fixtures) { 56 | return $this->getScenario()->runStep(new \Codeception\Step\Action('haveFixtures', func_get_args())); 57 | } 58 | 59 | 60 | /** 61 | * [!] Method is generated. Documentation taken from corresponding module. 62 | * 63 | * Returns all loaded fixtures. 64 | * Array of fixture instances 65 | * 66 | * @part fixtures 67 | * @return array 68 | * @see \Codeception\Module\Yii2::grabFixtures() 69 | */ 70 | public function grabFixtures() { 71 | return $this->getScenario()->runStep(new \Codeception\Step\Action('grabFixtures', func_get_args())); 72 | } 73 | 74 | 75 | /** 76 | * [!] Method is generated. Documentation taken from corresponding module. 77 | * 78 | * Gets a fixture by name. 79 | * Returns a Fixture instance. If a fixture is an instance of 80 | * `\yii\test\BaseActiveFixture` a second parameter can be used to return a 81 | * specific model: 82 | * 83 | * ```php 84 | * haveFixtures(['users' => UserFixture::className()]); 86 | * 87 | * $users = $I->grabFixture('users'); 88 | * 89 | * // get first user by key, if a fixture is an instance of ActiveFixture 90 | * $user = $I->grabFixture('users', 'user1'); 91 | * ``` 92 | * 93 | * @param $name 94 | * @return mixed 95 | * @throws ModuleException if the fixture is not found 96 | * @part fixtures 97 | * @see \Codeception\Module\Yii2::grabFixture() 98 | */ 99 | public function grabFixture($name, $index = NULL) { 100 | return $this->getScenario()->runStep(new \Codeception\Step\Action('grabFixture', func_get_args())); 101 | } 102 | 103 | 104 | /** 105 | * [!] Method is generated. Documentation taken from corresponding module. 106 | * 107 | * Inserts a record into the database. 108 | * 109 | * ``` php 110 | * haveRecord('app\models\User', array('name' => 'Davert')); 112 | * ?> 113 | * ``` 114 | * 115 | * @param $model 116 | * @param array $attributes 117 | * @return mixed 118 | * @part orm 119 | * @see \Codeception\Module\Yii2::haveRecord() 120 | */ 121 | public function haveRecord($model, $attributes = []) { 122 | return $this->getScenario()->runStep(new \Codeception\Step\Action('haveRecord', func_get_args())); 123 | } 124 | 125 | 126 | /** 127 | * [!] Method is generated. Documentation taken from corresponding module. 128 | * 129 | * Checks that a record exists in the database. 130 | * 131 | * ``` php 132 | * $I->seeRecord('app\models\User', array('name' => 'davert')); 133 | * ``` 134 | * 135 | * @param $model 136 | * @param array $attributes 137 | * @part orm 138 | * @see \Codeception\Module\Yii2::seeRecord() 139 | */ 140 | public function seeRecord($model, $attributes = []) { 141 | return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeRecord', func_get_args())); 142 | } 143 | 144 | 145 | /** 146 | * [!] Method is generated. Documentation taken from corresponding module. 147 | * 148 | * Checks that a record does not exist in the database. 149 | * 150 | * ``` php 151 | * $I->dontSeeRecord('app\models\User', array('name' => 'davert')); 152 | * ``` 153 | * 154 | * @param $model 155 | * @param array $attributes 156 | * @part orm 157 | * @see \Codeception\Module\Yii2::dontSeeRecord() 158 | */ 159 | public function dontSeeRecord($model, $attributes = []) { 160 | return $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeRecord', func_get_args())); 161 | } 162 | 163 | 164 | /** 165 | * [!] Method is generated. Documentation taken from corresponding module. 166 | * 167 | * Retrieves a record from the database 168 | * 169 | * ``` php 170 | * $category = $I->grabRecord('app\models\User', array('name' => 'davert')); 171 | * ``` 172 | * 173 | * @param $model 174 | * @param array $attributes 175 | * @return mixed 176 | * @part orm 177 | * @see \Codeception\Module\Yii2::grabRecord() 178 | */ 179 | public function grabRecord($model, $attributes = []) { 180 | return $this->getScenario()->runStep(new \Codeception\Step\Action('grabRecord', func_get_args())); 181 | } 182 | 183 | 184 | /** 185 | * [!] Method is generated. Documentation taken from corresponding module. 186 | * 187 | * Handles and checks exception called inside callback function. 188 | * Either exception class name or exception instance should be provided. 189 | * 190 | * ```php 191 | * expectException(MyException::class, function() { 193 | * $this->doSomethingBad(); 194 | * }); 195 | * 196 | * $I->expectException(new MyException(), function() { 197 | * $this->doSomethingBad(); 198 | * }); 199 | * ``` 200 | * If you want to check message or exception code, you can pass them with exception instance: 201 | * ```php 202 | * expectException(new MyException("Don't do bad things"), function() { 205 | * $this->doSomethingBad(); 206 | * }); 207 | * ``` 208 | * 209 | * @deprecated Use expectThrowable() instead 210 | * @param \Exception|string $exception 211 | * @param callable $callback 212 | * @see \Codeception\Module\Asserts::expectException() 213 | */ 214 | public function expectException($exception, $callback) { 215 | return $this->getScenario()->runStep(new \Codeception\Step\Action('expectException', func_get_args())); 216 | } 217 | 218 | 219 | /** 220 | * [!] Method is generated. Documentation taken from corresponding module. 221 | * 222 | * Handles and checks throwables (Exceptions/Errors) called inside the callback function. 223 | * Either throwable class name or throwable instance should be provided. 224 | * 225 | * ```php 226 | * expectThrowable(MyThrowable::class, function() { 228 | * $this->doSomethingBad(); 229 | * }); 230 | * 231 | * $I->expectThrowable(new MyException(), function() { 232 | * $this->doSomethingBad(); 233 | * }); 234 | * ``` 235 | * If you want to check message or throwable code, you can pass them with throwable instance: 236 | * ```php 237 | * expectThrowable(new MyError("Don't do bad things"), function() { 240 | * $this->doSomethingBad(); 241 | * }); 242 | * ``` 243 | * 244 | * @param \Throwable|string $throwable 245 | * @param callable $callback 246 | * @see \Codeception\Module\Asserts::expectThrowable() 247 | */ 248 | public function expectThrowable($throwable, $callback) { 249 | return $this->getScenario()->runStep(new \Codeception\Step\Action('expectThrowable', func_get_args())); 250 | } 251 | 252 | 253 | /** 254 | * [!] Method is generated. Documentation taken from corresponding module. 255 | * 256 | * Asserts that a file does not exist. 257 | * 258 | * @param string $filename 259 | * @param string $message 260 | * @see \Codeception\Module\AbstractAsserts::assertFileNotExists() 261 | */ 262 | public function assertFileNotExists($filename, $message = "") { 263 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileNotExists', func_get_args())); 264 | } 265 | 266 | 267 | /** 268 | * [!] Method is generated. Documentation taken from corresponding module. 269 | * 270 | * Asserts that a value is greater than or equal to another value. 271 | * 272 | * @param $expected 273 | * @param $actual 274 | * @param string $message 275 | * @see \Codeception\Module\AbstractAsserts::assertGreaterOrEquals() 276 | */ 277 | public function assertGreaterOrEquals($expected, $actual, $message = "") { 278 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertGreaterOrEquals', func_get_args())); 279 | } 280 | 281 | 282 | /** 283 | * [!] Method is generated. Documentation taken from corresponding module. 284 | * 285 | * Asserts that a variable is empty. 286 | * 287 | * @param $actual 288 | * @param string $message 289 | * @see \Codeception\Module\AbstractAsserts::assertIsEmpty() 290 | */ 291 | public function assertIsEmpty($actual, $message = "") { 292 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsEmpty', func_get_args())); 293 | } 294 | 295 | 296 | /** 297 | * [!] Method is generated. Documentation taken from corresponding module. 298 | * 299 | * Asserts that a value is smaller than or equal to another value. 300 | * 301 | * @param $expected 302 | * @param $actual 303 | * @param string $message 304 | * @see \Codeception\Module\AbstractAsserts::assertLessOrEquals() 305 | */ 306 | public function assertLessOrEquals($expected, $actual, $message = "") { 307 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertLessOrEquals', func_get_args())); 308 | } 309 | 310 | 311 | /** 312 | * [!] Method is generated. Documentation taken from corresponding module. 313 | * 314 | * Asserts that a string does not match a given regular expression. 315 | * 316 | * @param string $pattern 317 | * @param string $string 318 | * @param string $message 319 | * @see \Codeception\Module\AbstractAsserts::assertNotRegExp() 320 | */ 321 | public function assertNotRegExp($pattern, $string, $message = "") { 322 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotRegExp', func_get_args())); 323 | } 324 | 325 | 326 | /** 327 | * [!] Method is generated. Documentation taken from corresponding module. 328 | * 329 | * Asserts that a string matches a given regular expression. 330 | * 331 | * @param string $pattern 332 | * @param string $string 333 | * @param string $message 334 | * @see \Codeception\Module\AbstractAsserts::assertRegExp() 335 | */ 336 | public function assertRegExp($pattern, $string, $message = "") { 337 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertRegExp', func_get_args())); 338 | } 339 | 340 | 341 | /** 342 | * [!] Method is generated. Documentation taken from corresponding module. 343 | * 344 | * Evaluates a PHPUnit\Framework\Constraint matcher object. 345 | * 346 | * @param $value 347 | * @param Constraint $constraint 348 | * @param string $message 349 | * @see \Codeception\Module\AbstractAsserts::assertThatItsNot() 350 | */ 351 | public function assertThatItsNot($value, $constraint, $message = "") { 352 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertThatItsNot', func_get_args())); 353 | } 354 | 355 | 356 | /** 357 | * [!] Method is generated. Documentation taken from corresponding module. 358 | * 359 | * Asserts that an array has a specified key. 360 | * 361 | * @param int|string $key 362 | * @param array|ArrayAccess $array 363 | * @param string $message 364 | * @see \Codeception\Module\AbstractAsserts::assertArrayHasKey() 365 | */ 366 | public function assertArrayHasKey($key, $array, $message = "") { 367 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertArrayHasKey', func_get_args())); 368 | } 369 | 370 | 371 | /** 372 | * [!] Method is generated. Documentation taken from corresponding module. 373 | * 374 | * Asserts that an array does not have a specified key. 375 | * 376 | * @param int|string $key 377 | * @param array|ArrayAccess $array 378 | * @param string $message 379 | * @see \Codeception\Module\AbstractAsserts::assertArrayNotHasKey() 380 | */ 381 | public function assertArrayNotHasKey($key, $array, $message = "") { 382 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertArrayNotHasKey', func_get_args())); 383 | } 384 | 385 | 386 | /** 387 | * [!] Method is generated. Documentation taken from corresponding module. 388 | * 389 | * Asserts that a class has a specified attribute. 390 | * 391 | * @param string $attributeName 392 | * @param string $className 393 | * @param string $message 394 | * @see \Codeception\Module\AbstractAsserts::assertClassHasAttribute() 395 | */ 396 | public function assertClassHasAttribute($attributeName, $className, $message = "") { 397 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertClassHasAttribute', func_get_args())); 398 | } 399 | 400 | 401 | /** 402 | * [!] Method is generated. Documentation taken from corresponding module. 403 | * 404 | * Asserts that a class has a specified static attribute. 405 | * 406 | * @param string $attributeName 407 | * @param string $className 408 | * @param string $message 409 | * @see \Codeception\Module\AbstractAsserts::assertClassHasStaticAttribute() 410 | */ 411 | public function assertClassHasStaticAttribute($attributeName, $className, $message = "") { 412 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertClassHasStaticAttribute', func_get_args())); 413 | } 414 | 415 | 416 | /** 417 | * [!] Method is generated. Documentation taken from corresponding module. 418 | * 419 | * Asserts that a class does not have a specified attribute. 420 | * 421 | * @param string $attributeName 422 | * @param string $className 423 | * @param string $message 424 | * @see \Codeception\Module\AbstractAsserts::assertClassNotHasAttribute() 425 | */ 426 | public function assertClassNotHasAttribute($attributeName, $className, $message = "") { 427 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertClassNotHasAttribute', func_get_args())); 428 | } 429 | 430 | 431 | /** 432 | * [!] Method is generated. Documentation taken from corresponding module. 433 | * 434 | * Asserts that a class does not have a specified static attribute. 435 | * 436 | * @param string $attributeName 437 | * @param string $className 438 | * @param string $message 439 | * @see \Codeception\Module\AbstractAsserts::assertClassNotHasStaticAttribute() 440 | */ 441 | public function assertClassNotHasStaticAttribute($attributeName, $className, $message = "") { 442 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertClassNotHasStaticAttribute', func_get_args())); 443 | } 444 | 445 | 446 | /** 447 | * [!] Method is generated. Documentation taken from corresponding module. 448 | * 449 | * Asserts that a haystack contains a needle. 450 | * 451 | * @param $needle 452 | * @param $haystack 453 | * @param string $message 454 | * @see \Codeception\Module\AbstractAsserts::assertContains() 455 | */ 456 | public function assertContains($needle, $haystack, $message = "") { 457 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertContains', func_get_args())); 458 | } 459 | 460 | 461 | /** 462 | * [!] Method is generated. Documentation taken from corresponding module. 463 | * 464 | * @param $needle 465 | * @param $haystack 466 | * @param string $message 467 | * @see \Codeception\Module\AbstractAsserts::assertContainsEquals() 468 | */ 469 | public function assertContainsEquals($needle, $haystack, $message = "") { 470 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertContainsEquals', func_get_args())); 471 | } 472 | 473 | 474 | /** 475 | * [!] Method is generated. Documentation taken from corresponding module. 476 | * 477 | * Asserts that a haystack contains only values of a given type. 478 | * 479 | * @param string $type 480 | * @param $haystack 481 | * @param bool|null $isNativeType 482 | * @param string $message 483 | * @see \Codeception\Module\AbstractAsserts::assertContainsOnly() 484 | */ 485 | public function assertContainsOnly($type, $haystack, $isNativeType = NULL, $message = "") { 486 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertContainsOnly', func_get_args())); 487 | } 488 | 489 | 490 | /** 491 | * [!] Method is generated. Documentation taken from corresponding module. 492 | * 493 | * Asserts that a haystack contains only instances of a given class name. 494 | * 495 | * @param string $className 496 | * @param $haystack 497 | * @param string $message 498 | * @see \Codeception\Module\AbstractAsserts::assertContainsOnlyInstancesOf() 499 | */ 500 | public function assertContainsOnlyInstancesOf($className, $haystack, $message = "") { 501 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertContainsOnlyInstancesOf', func_get_args())); 502 | } 503 | 504 | 505 | /** 506 | * [!] Method is generated. Documentation taken from corresponding module. 507 | * 508 | * Asserts the number of elements of an array, Countable or Traversable. 509 | * 510 | * @param int $expectedCount 511 | * @param Countable|iterable $haystack 512 | * @param string $message 513 | * @see \Codeception\Module\AbstractAsserts::assertCount() 514 | */ 515 | public function assertCount($expectedCount, $haystack, $message = "") { 516 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertCount', func_get_args())); 517 | } 518 | 519 | 520 | /** 521 | * [!] Method is generated. Documentation taken from corresponding module. 522 | * 523 | * Asserts that a directory does not exist. 524 | * 525 | * @param string $directory 526 | * @param string $message 527 | * @see \Codeception\Module\AbstractAsserts::assertDirectoryDoesNotExist() 528 | */ 529 | public function assertDirectoryDoesNotExist($directory, $message = "") { 530 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertDirectoryDoesNotExist', func_get_args())); 531 | } 532 | 533 | 534 | /** 535 | * [!] Method is generated. Documentation taken from corresponding module. 536 | * 537 | * Asserts that a directory exists. 538 | * 539 | * @param string $directory 540 | * @param string $message 541 | * @see \Codeception\Module\AbstractAsserts::assertDirectoryExists() 542 | */ 543 | public function assertDirectoryExists($directory, $message = "") { 544 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertDirectoryExists', func_get_args())); 545 | } 546 | 547 | 548 | /** 549 | * [!] Method is generated. Documentation taken from corresponding module. 550 | * 551 | * Asserts that a directory exists and is not readable. 552 | * 553 | * @param string $directory 554 | * @param string $message 555 | * @see \Codeception\Module\AbstractAsserts::assertDirectoryIsNotReadable() 556 | */ 557 | public function assertDirectoryIsNotReadable($directory, $message = "") { 558 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertDirectoryIsNotReadable', func_get_args())); 559 | } 560 | 561 | 562 | /** 563 | * [!] Method is generated. Documentation taken from corresponding module. 564 | * 565 | * Asserts that a directory exists and is not writable. 566 | * 567 | * @param string $directory 568 | * @param string $message 569 | * @see \Codeception\Module\AbstractAsserts::assertDirectoryIsNotWritable() 570 | */ 571 | public function assertDirectoryIsNotWritable($directory, $message = "") { 572 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertDirectoryIsNotWritable', func_get_args())); 573 | } 574 | 575 | 576 | /** 577 | * [!] Method is generated. Documentation taken from corresponding module. 578 | * 579 | * Asserts that a directory exists and is readable. 580 | * 581 | * @param string $directory 582 | * @param string $message 583 | * @see \Codeception\Module\AbstractAsserts::assertDirectoryIsReadable() 584 | */ 585 | public function assertDirectoryIsReadable($directory, $message = "") { 586 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertDirectoryIsReadable', func_get_args())); 587 | } 588 | 589 | 590 | /** 591 | * [!] Method is generated. Documentation taken from corresponding module. 592 | * 593 | * Asserts that a directory exists and is writable. 594 | * 595 | * @param string $directory 596 | * @param string $message 597 | * @see \Codeception\Module\AbstractAsserts::assertDirectoryIsWritable() 598 | */ 599 | public function assertDirectoryIsWritable($directory, $message = "") { 600 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertDirectoryIsWritable', func_get_args())); 601 | } 602 | 603 | 604 | /** 605 | * [!] Method is generated. Documentation taken from corresponding module. 606 | * 607 | * Asserts that a string does not match a given regular expression. 608 | * 609 | * @param string $pattern 610 | * @param string $string 611 | * @param string $message 612 | * @see \Codeception\Module\AbstractAsserts::assertDoesNotMatchRegularExpression() 613 | */ 614 | public function assertDoesNotMatchRegularExpression($pattern, $string, $message = "") { 615 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertDoesNotMatchRegularExpression', func_get_args())); 616 | } 617 | 618 | 619 | /** 620 | * [!] Method is generated. Documentation taken from corresponding module. 621 | * 622 | * Asserts that a variable is empty. 623 | * 624 | * @param $actual 625 | * @param string $message 626 | * @see \Codeception\Module\AbstractAsserts::assertEmpty() 627 | */ 628 | public function assertEmpty($actual, $message = "") { 629 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertEmpty', func_get_args())); 630 | } 631 | 632 | 633 | /** 634 | * [!] Method is generated. Documentation taken from corresponding module. 635 | * 636 | * Asserts that two variables are equal. 637 | * 638 | * @param $expected 639 | * @param $actual 640 | * @param string $message 641 | * @see \Codeception\Module\AbstractAsserts::assertEquals() 642 | */ 643 | public function assertEquals($expected, $actual, $message = "") { 644 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertEquals', func_get_args())); 645 | } 646 | 647 | 648 | /** 649 | * [!] Method is generated. Documentation taken from corresponding module. 650 | * 651 | * Asserts that two variables are equal (canonicalizing). 652 | * 653 | * @param $expected 654 | * @param $actual 655 | * @param string $message 656 | * @see \Codeception\Module\AbstractAsserts::assertEqualsCanonicalizing() 657 | */ 658 | public function assertEqualsCanonicalizing($expected, $actual, $message = "") { 659 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertEqualsCanonicalizing', func_get_args())); 660 | } 661 | 662 | 663 | /** 664 | * [!] Method is generated. Documentation taken from corresponding module. 665 | * 666 | * Asserts that two variables are equal (ignoring case). 667 | * 668 | * @param $expected 669 | * @param $actual 670 | * @param string $message 671 | * @see \Codeception\Module\AbstractAsserts::assertEqualsIgnoringCase() 672 | */ 673 | public function assertEqualsIgnoringCase($expected, $actual, $message = "") { 674 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertEqualsIgnoringCase', func_get_args())); 675 | } 676 | 677 | 678 | /** 679 | * [!] Method is generated. Documentation taken from corresponding module. 680 | * 681 | * Asserts that two variables are equal (with delta). 682 | * 683 | * @param $expected 684 | * @param $actual 685 | * @param float $delta 686 | * @param string $message 687 | * @see \Codeception\Module\AbstractAsserts::assertEqualsWithDelta() 688 | */ 689 | public function assertEqualsWithDelta($expected, $actual, $delta, $message = "") { 690 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertEqualsWithDelta', func_get_args())); 691 | } 692 | 693 | 694 | /** 695 | * [!] Method is generated. Documentation taken from corresponding module. 696 | * 697 | * Asserts that a condition is false. 698 | * 699 | * @param $condition 700 | * @param string $message 701 | * @see \Codeception\Module\AbstractAsserts::assertFalse() 702 | */ 703 | public function assertFalse($condition, $message = "") { 704 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFalse', func_get_args())); 705 | } 706 | 707 | 708 | /** 709 | * [!] Method is generated. Documentation taken from corresponding module. 710 | * 711 | * Asserts that a file does not exist. 712 | * 713 | * @param string $filename 714 | * @param string $message 715 | * @see \Codeception\Module\AbstractAsserts::assertFileDoesNotExist() 716 | */ 717 | public function assertFileDoesNotExist($filename, $message = "") { 718 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileDoesNotExist', func_get_args())); 719 | } 720 | 721 | 722 | /** 723 | * [!] Method is generated. Documentation taken from corresponding module. 724 | * 725 | * Asserts that the contents of one file is equal to the contents of another file. 726 | * 727 | * @param string $expected 728 | * @param string $actual 729 | * @param string $message 730 | * @see \Codeception\Module\AbstractAsserts::assertFileEquals() 731 | */ 732 | public function assertFileEquals($expected, $actual, $message = "") { 733 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileEquals', func_get_args())); 734 | } 735 | 736 | 737 | /** 738 | * [!] Method is generated. Documentation taken from corresponding module. 739 | * 740 | * Asserts that the contents of one file is equal to the contents of another file (canonicalizing). 741 | * 742 | * @param $expected 743 | * @param $actual 744 | * @param string $message 745 | * @see \Codeception\Module\AbstractAsserts::assertFileEqualsCanonicalizing() 746 | */ 747 | public function assertFileEqualsCanonicalizing($expected, $actual, $message = "") { 748 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileEqualsCanonicalizing', func_get_args())); 749 | } 750 | 751 | 752 | /** 753 | * [!] Method is generated. Documentation taken from corresponding module. 754 | * 755 | * Asserts that the contents of one file is equal to the contents of another file (ignoring case). 756 | * 757 | * @param $expected 758 | * @param $actual 759 | * @param string $message 760 | * @see \Codeception\Module\AbstractAsserts::assertFileEqualsIgnoringCase() 761 | */ 762 | public function assertFileEqualsIgnoringCase($expected, $actual, $message = "") { 763 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileEqualsIgnoringCase', func_get_args())); 764 | } 765 | 766 | 767 | /** 768 | * [!] Method is generated. Documentation taken from corresponding module. 769 | * 770 | * Asserts that a file exists. 771 | * 772 | * @param string $filename 773 | * @param string $message 774 | * @see \Codeception\Module\AbstractAsserts::assertFileExists() 775 | */ 776 | public function assertFileExists($filename, $message = "") { 777 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileExists', func_get_args())); 778 | } 779 | 780 | 781 | /** 782 | * [!] Method is generated. Documentation taken from corresponding module. 783 | * 784 | * Asserts that a file exists and is not readable. 785 | * 786 | * @param string $file 787 | * @param string $message 788 | * @see \Codeception\Module\AbstractAsserts::assertFileIsNotReadable() 789 | */ 790 | public function assertFileIsNotReadable($file, $message = "") { 791 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileIsNotReadable', func_get_args())); 792 | } 793 | 794 | 795 | /** 796 | * [!] Method is generated. Documentation taken from corresponding module. 797 | * 798 | * Asserts that a file exists and is not writable. 799 | * 800 | * @param string $file 801 | * @param string $message 802 | * @see \Codeception\Module\AbstractAsserts::assertFileIsNotWritable() 803 | */ 804 | public function assertFileIsNotWritable($file, $message = "") { 805 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileIsNotWritable', func_get_args())); 806 | } 807 | 808 | 809 | /** 810 | * [!] Method is generated. Documentation taken from corresponding module. 811 | * 812 | * Asserts that a file exists and is readable. 813 | * 814 | * @param string $file 815 | * @param string $message 816 | * @see \Codeception\Module\AbstractAsserts::assertFileIsReadable() 817 | */ 818 | public function assertFileIsReadable($file, $message = "") { 819 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileIsReadable', func_get_args())); 820 | } 821 | 822 | 823 | /** 824 | * [!] Method is generated. Documentation taken from corresponding module. 825 | * 826 | * Asserts that a file exists and is writable. 827 | * 828 | * @param string $file 829 | * @param string $message 830 | * @see \Codeception\Module\AbstractAsserts::assertFileIsWritable() 831 | */ 832 | public function assertFileIsWritable($file, $message = "") { 833 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileIsWritable', func_get_args())); 834 | } 835 | 836 | 837 | /** 838 | * [!] Method is generated. Documentation taken from corresponding module. 839 | * 840 | * Asserts that the contents of one file is not equal to the contents of another file. 841 | * 842 | * @param $expected 843 | * @param $actual 844 | * @param string $message 845 | * @see \Codeception\Module\AbstractAsserts::assertFileNotEquals() 846 | */ 847 | public function assertFileNotEquals($expected, $actual, $message = "") { 848 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileNotEquals', func_get_args())); 849 | } 850 | 851 | 852 | /** 853 | * [!] Method is generated. Documentation taken from corresponding module. 854 | * 855 | * Asserts that the contents of one file is not equal to the contents of another file (canonicalizing). 856 | * 857 | * @param $expected 858 | * @param $actual 859 | * @param string $message 860 | * @see \Codeception\Module\AbstractAsserts::assertFileNotEqualsCanonicalizing() 861 | */ 862 | public function assertFileNotEqualsCanonicalizing($expected, $actual, $message = "") { 863 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileNotEqualsCanonicalizing', func_get_args())); 864 | } 865 | 866 | 867 | /** 868 | * [!] Method is generated. Documentation taken from corresponding module. 869 | * 870 | * Asserts that the contents of one file is not equal to the contents of another file (ignoring case). 871 | * 872 | * @param $expected 873 | * @param $actual 874 | * @param string $message 875 | * @see \Codeception\Module\AbstractAsserts::assertFileNotEqualsIgnoringCase() 876 | */ 877 | public function assertFileNotEqualsIgnoringCase($expected, $actual, $message = "") { 878 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileNotEqualsIgnoringCase', func_get_args())); 879 | } 880 | 881 | 882 | /** 883 | * [!] Method is generated. Documentation taken from corresponding module. 884 | * 885 | * Asserts that a variable is finite. 886 | * 887 | * @param $actual 888 | * @param string $message 889 | * @see \Codeception\Module\AbstractAsserts::assertFinite() 890 | */ 891 | public function assertFinite($actual, $message = "") { 892 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFinite', func_get_args())); 893 | } 894 | 895 | 896 | /** 897 | * [!] Method is generated. Documentation taken from corresponding module. 898 | * 899 | * Asserts that a value is greater than another value. 900 | * 901 | * @param $expected 902 | * @param $actual 903 | * @param string $message 904 | * @see \Codeception\Module\AbstractAsserts::assertGreaterThan() 905 | */ 906 | public function assertGreaterThan($expected, $actual, $message = "") { 907 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertGreaterThan', func_get_args())); 908 | } 909 | 910 | 911 | /** 912 | * [!] Method is generated. Documentation taken from corresponding module. 913 | * 914 | * Asserts that a value is greater than or equal to another value. 915 | * 916 | * @param $expected 917 | * @param $actual 918 | * @param string $message 919 | * @see \Codeception\Module\AbstractAsserts::assertGreaterThanOrEqual() 920 | */ 921 | public function assertGreaterThanOrEqual($expected, $actual, $message = "") { 922 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertGreaterThanOrEqual', func_get_args())); 923 | } 924 | 925 | 926 | /** 927 | * [!] Method is generated. Documentation taken from corresponding module. 928 | * 929 | * Asserts that a variable is infinite. 930 | * 931 | * @param $actual 932 | * @param string $message 933 | * @see \Codeception\Module\AbstractAsserts::assertInfinite() 934 | */ 935 | public function assertInfinite($actual, $message = "") { 936 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertInfinite', func_get_args())); 937 | } 938 | 939 | 940 | /** 941 | * [!] Method is generated. Documentation taken from corresponding module. 942 | * 943 | * Asserts that a variable is of a given type. 944 | * 945 | * @param $expected 946 | * @param $actual 947 | * @param string $message 948 | * @see \Codeception\Module\AbstractAsserts::assertInstanceOf() 949 | */ 950 | public function assertInstanceOf($expected, $actual, $message = "") { 951 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertInstanceOf', func_get_args())); 952 | } 953 | 954 | 955 | /** 956 | * [!] Method is generated. Documentation taken from corresponding module. 957 | * 958 | * Asserts that a variable is of type array. 959 | * 960 | * @param $actual 961 | * @param string $message 962 | * @see \Codeception\Module\AbstractAsserts::assertIsArray() 963 | */ 964 | public function assertIsArray($actual, $message = "") { 965 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsArray', func_get_args())); 966 | } 967 | 968 | 969 | /** 970 | * [!] Method is generated. Documentation taken from corresponding module. 971 | * 972 | * Asserts that a variable is of type bool. 973 | * 974 | * @param $actual 975 | * @param string $message 976 | * @see \Codeception\Module\AbstractAsserts::assertIsBool() 977 | */ 978 | public function assertIsBool($actual, $message = "") { 979 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsBool', func_get_args())); 980 | } 981 | 982 | 983 | /** 984 | * [!] Method is generated. Documentation taken from corresponding module. 985 | * 986 | * Asserts that a variable is of type callable. 987 | * 988 | * @param $actual 989 | * @param string $message 990 | * @see \Codeception\Module\AbstractAsserts::assertIsCallable() 991 | */ 992 | public function assertIsCallable($actual, $message = "") { 993 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsCallable', func_get_args())); 994 | } 995 | 996 | 997 | /** 998 | * [!] Method is generated. Documentation taken from corresponding module. 999 | * 1000 | * Asserts that a variable is of type resource and is closed. 1001 | * 1002 | * @param $actual 1003 | * @param string $message 1004 | * @see \Codeception\Module\AbstractAsserts::assertIsClosedResource() 1005 | */ 1006 | public function assertIsClosedResource($actual, $message = "") { 1007 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsClosedResource', func_get_args())); 1008 | } 1009 | 1010 | 1011 | /** 1012 | * [!] Method is generated. Documentation taken from corresponding module. 1013 | * 1014 | * Asserts that a variable is of type float. 1015 | * 1016 | * @param $actual 1017 | * @param string $message 1018 | * @see \Codeception\Module\AbstractAsserts::assertIsFloat() 1019 | */ 1020 | public function assertIsFloat($actual, $message = "") { 1021 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsFloat', func_get_args())); 1022 | } 1023 | 1024 | 1025 | /** 1026 | * [!] Method is generated. Documentation taken from corresponding module. 1027 | * 1028 | * Asserts that a variable is of type int. 1029 | * 1030 | * @param $actual 1031 | * @param string $message 1032 | * @see \Codeception\Module\AbstractAsserts::assertIsInt() 1033 | */ 1034 | public function assertIsInt($actual, $message = "") { 1035 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsInt', func_get_args())); 1036 | } 1037 | 1038 | 1039 | /** 1040 | * [!] Method is generated. Documentation taken from corresponding module. 1041 | * 1042 | * Asserts that a variable is of type iterable. 1043 | * 1044 | * @param $actual 1045 | * @param string $message 1046 | * @see \Codeception\Module\AbstractAsserts::assertIsIterable() 1047 | */ 1048 | public function assertIsIterable($actual, $message = "") { 1049 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsIterable', func_get_args())); 1050 | } 1051 | 1052 | 1053 | /** 1054 | * [!] Method is generated. Documentation taken from corresponding module. 1055 | * 1056 | * Asserts that a variable is not of type array. 1057 | * 1058 | * @param $actual 1059 | * @param string $message 1060 | * @see \Codeception\Module\AbstractAsserts::assertIsNotArray() 1061 | */ 1062 | public function assertIsNotArray($actual, $message = "") { 1063 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotArray', func_get_args())); 1064 | } 1065 | 1066 | 1067 | /** 1068 | * [!] Method is generated. Documentation taken from corresponding module. 1069 | * 1070 | * Asserts that a variable is not of type bool. 1071 | * 1072 | * @param $actual 1073 | * @param string $message 1074 | * @see \Codeception\Module\AbstractAsserts::assertIsNotBool() 1075 | */ 1076 | public function assertIsNotBool($actual, $message = "") { 1077 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotBool', func_get_args())); 1078 | } 1079 | 1080 | 1081 | /** 1082 | * [!] Method is generated. Documentation taken from corresponding module. 1083 | * 1084 | * Asserts that a variable is not of type callable. 1085 | * 1086 | * @param $actual 1087 | * @param string $message 1088 | * @see \Codeception\Module\AbstractAsserts::assertIsNotCallable() 1089 | */ 1090 | public function assertIsNotCallable($actual, $message = "") { 1091 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotCallable', func_get_args())); 1092 | } 1093 | 1094 | 1095 | /** 1096 | * [!] Method is generated. Documentation taken from corresponding module. 1097 | * 1098 | * Asserts that a variable is not of type resource. 1099 | * 1100 | * @param $actual 1101 | * @param string $message 1102 | * @see \Codeception\Module\AbstractAsserts::assertIsNotClosedResource() 1103 | */ 1104 | public function assertIsNotClosedResource($actual, $message = "") { 1105 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotClosedResource', func_get_args())); 1106 | } 1107 | 1108 | 1109 | /** 1110 | * [!] Method is generated. Documentation taken from corresponding module. 1111 | * 1112 | * Asserts that a variable is not of type float. 1113 | * 1114 | * @param $actual 1115 | * @param string $message 1116 | * @see \Codeception\Module\AbstractAsserts::assertIsNotFloat() 1117 | */ 1118 | public function assertIsNotFloat($actual, $message = "") { 1119 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotFloat', func_get_args())); 1120 | } 1121 | 1122 | 1123 | /** 1124 | * [!] Method is generated. Documentation taken from corresponding module. 1125 | * 1126 | * Asserts that a variable is not of type int. 1127 | * 1128 | * @param $actual 1129 | * @param string $message 1130 | * @see \Codeception\Module\AbstractAsserts::assertIsNotInt() 1131 | */ 1132 | public function assertIsNotInt($actual, $message = "") { 1133 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotInt', func_get_args())); 1134 | } 1135 | 1136 | 1137 | /** 1138 | * [!] Method is generated. Documentation taken from corresponding module. 1139 | * 1140 | * Asserts that a variable is not of type iterable. 1141 | * 1142 | * @param $actual 1143 | * @param string $message 1144 | * @see \Codeception\Module\AbstractAsserts::assertIsNotIterable() 1145 | */ 1146 | public function assertIsNotIterable($actual, $message = "") { 1147 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotIterable', func_get_args())); 1148 | } 1149 | 1150 | 1151 | /** 1152 | * [!] Method is generated. Documentation taken from corresponding module. 1153 | * 1154 | * Asserts that a variable is not of type numeric. 1155 | * 1156 | * @param $actual 1157 | * @param string $message 1158 | * @see \Codeception\Module\AbstractAsserts::assertIsNotNumeric() 1159 | */ 1160 | public function assertIsNotNumeric($actual, $message = "") { 1161 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotNumeric', func_get_args())); 1162 | } 1163 | 1164 | 1165 | /** 1166 | * [!] Method is generated. Documentation taken from corresponding module. 1167 | * 1168 | * Asserts that a variable is not of type object. 1169 | * 1170 | * @param $actual 1171 | * @param string $message 1172 | * @see \Codeception\Module\AbstractAsserts::assertIsNotObject() 1173 | */ 1174 | public function assertIsNotObject($actual, $message = "") { 1175 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotObject', func_get_args())); 1176 | } 1177 | 1178 | 1179 | /** 1180 | * [!] Method is generated. Documentation taken from corresponding module. 1181 | * 1182 | * Asserts that a file/dir exists and is not readable. 1183 | * 1184 | * @param string $filename 1185 | * @param string $message 1186 | * @see \Codeception\Module\AbstractAsserts::assertIsNotReadable() 1187 | */ 1188 | public function assertIsNotReadable($filename, $message = "") { 1189 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotReadable', func_get_args())); 1190 | } 1191 | 1192 | 1193 | /** 1194 | * [!] Method is generated. Documentation taken from corresponding module. 1195 | * 1196 | * Asserts that a variable is not of type resource. 1197 | * 1198 | * @param $actual 1199 | * @param string $message 1200 | * @see \Codeception\Module\AbstractAsserts::assertIsNotResource() 1201 | */ 1202 | public function assertIsNotResource($actual, $message = "") { 1203 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotResource', func_get_args())); 1204 | } 1205 | 1206 | 1207 | /** 1208 | * [!] Method is generated. Documentation taken from corresponding module. 1209 | * 1210 | * Asserts that a variable is not of type scalar. 1211 | * 1212 | * @param $actual 1213 | * @param string $message 1214 | * @see \Codeception\Module\AbstractAsserts::assertIsNotScalar() 1215 | */ 1216 | public function assertIsNotScalar($actual, $message = "") { 1217 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotScalar', func_get_args())); 1218 | } 1219 | 1220 | 1221 | /** 1222 | * [!] Method is generated. Documentation taken from corresponding module. 1223 | * 1224 | * Asserts that a variable is not of type string. 1225 | * 1226 | * @param $actual 1227 | * @param string $message 1228 | * @see \Codeception\Module\AbstractAsserts::assertIsNotString() 1229 | */ 1230 | public function assertIsNotString($actual, $message = "") { 1231 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotString', func_get_args())); 1232 | } 1233 | 1234 | 1235 | /** 1236 | * [!] Method is generated. Documentation taken from corresponding module. 1237 | * 1238 | * Asserts that a file/dir exists and is not writable. 1239 | * 1240 | * @param $filename 1241 | * @param string $message 1242 | * @see \Codeception\Module\AbstractAsserts::assertIsNotWritable() 1243 | */ 1244 | public function assertIsNotWritable($filename, $message = "") { 1245 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotWritable', func_get_args())); 1246 | } 1247 | 1248 | 1249 | /** 1250 | * [!] Method is generated. Documentation taken from corresponding module. 1251 | * 1252 | * Asserts that a variable is of type numeric. 1253 | * 1254 | * @param $actual 1255 | * @param string $message 1256 | * @see \Codeception\Module\AbstractAsserts::assertIsNumeric() 1257 | */ 1258 | public function assertIsNumeric($actual, $message = "") { 1259 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNumeric', func_get_args())); 1260 | } 1261 | 1262 | 1263 | /** 1264 | * [!] Method is generated. Documentation taken from corresponding module. 1265 | * 1266 | * Asserts that a variable is of type object. 1267 | * 1268 | * @param $actual 1269 | * @param string $message 1270 | * @see \Codeception\Module\AbstractAsserts::assertIsObject() 1271 | */ 1272 | public function assertIsObject($actual, $message = "") { 1273 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsObject', func_get_args())); 1274 | } 1275 | 1276 | 1277 | /** 1278 | * [!] Method is generated. Documentation taken from corresponding module. 1279 | * 1280 | * Asserts that a file/dir is readable. 1281 | * 1282 | * @param $filename 1283 | * @param string $message 1284 | * @see \Codeception\Module\AbstractAsserts::assertIsReadable() 1285 | */ 1286 | public function assertIsReadable($filename, $message = "") { 1287 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsReadable', func_get_args())); 1288 | } 1289 | 1290 | 1291 | /** 1292 | * [!] Method is generated. Documentation taken from corresponding module. 1293 | * 1294 | * Asserts that a variable is of type resource. 1295 | * 1296 | * @param $actual 1297 | * @param string $message 1298 | * @see \Codeception\Module\AbstractAsserts::assertIsResource() 1299 | */ 1300 | public function assertIsResource($actual, $message = "") { 1301 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsResource', func_get_args())); 1302 | } 1303 | 1304 | 1305 | /** 1306 | * [!] Method is generated. Documentation taken from corresponding module. 1307 | * 1308 | * Asserts that a variable is of type scalar. 1309 | * 1310 | * @param $actual 1311 | * @param string $message 1312 | * @see \Codeception\Module\AbstractAsserts::assertIsScalar() 1313 | */ 1314 | public function assertIsScalar($actual, $message = "") { 1315 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsScalar', func_get_args())); 1316 | } 1317 | 1318 | 1319 | /** 1320 | * [!] Method is generated. Documentation taken from corresponding module. 1321 | * 1322 | * Asserts that a variable is of type string. 1323 | * 1324 | * @param $actual 1325 | * @param string $message 1326 | * @see \Codeception\Module\AbstractAsserts::assertIsString() 1327 | */ 1328 | public function assertIsString($actual, $message = "") { 1329 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsString', func_get_args())); 1330 | } 1331 | 1332 | 1333 | /** 1334 | * [!] Method is generated. Documentation taken from corresponding module. 1335 | * 1336 | * Asserts that a file/dir exists and is writable. 1337 | * 1338 | * @param $filename 1339 | * @param string $message 1340 | * @see \Codeception\Module\AbstractAsserts::assertIsWritable() 1341 | */ 1342 | public function assertIsWritable($filename, $message = "") { 1343 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsWritable', func_get_args())); 1344 | } 1345 | 1346 | 1347 | /** 1348 | * [!] Method is generated. Documentation taken from corresponding module. 1349 | * 1350 | * Asserts that a string is a valid JSON string. 1351 | * 1352 | * @param string $actualJson 1353 | * @param string $message 1354 | * @see \Codeception\Module\AbstractAsserts::assertJson() 1355 | */ 1356 | public function assertJson($actualJson, $message = "") { 1357 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertJson', func_get_args())); 1358 | } 1359 | 1360 | 1361 | /** 1362 | * [!] Method is generated. Documentation taken from corresponding module. 1363 | * 1364 | * Asserts that two JSON files are equal. 1365 | * 1366 | * @param string $expectedFile 1367 | * @param string $actualFile 1368 | * @param string $message 1369 | * @see \Codeception\Module\AbstractAsserts::assertJsonFileEqualsJsonFile() 1370 | */ 1371 | public function assertJsonFileEqualsJsonFile($expectedFile, $actualFile, $message = "") { 1372 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertJsonFileEqualsJsonFile', func_get_args())); 1373 | } 1374 | 1375 | 1376 | /** 1377 | * [!] Method is generated. Documentation taken from corresponding module. 1378 | * 1379 | * Asserts that two JSON files are not equal. 1380 | * 1381 | * @param string $expectedFile 1382 | * @param string $actualFile 1383 | * @param string $message 1384 | * @see \Codeception\Module\AbstractAsserts::assertJsonFileNotEqualsJsonFile() 1385 | */ 1386 | public function assertJsonFileNotEqualsJsonFile($expectedFile, $actualFile, $message = "") { 1387 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertJsonFileNotEqualsJsonFile', func_get_args())); 1388 | } 1389 | 1390 | 1391 | /** 1392 | * [!] Method is generated. Documentation taken from corresponding module. 1393 | * 1394 | * Asserts that the generated JSON encoded object and the content of the given file are equal. 1395 | * 1396 | * @param string $expectedFile 1397 | * @param string $actualJson 1398 | * @param string $message 1399 | * @see \Codeception\Module\AbstractAsserts::assertJsonStringEqualsJsonFile() 1400 | */ 1401 | public function assertJsonStringEqualsJsonFile($expectedFile, $actualJson, $message = "") { 1402 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertJsonStringEqualsJsonFile', func_get_args())); 1403 | } 1404 | 1405 | 1406 | /** 1407 | * [!] Method is generated. Documentation taken from corresponding module. 1408 | * 1409 | * Asserts that two given JSON encoded objects or arrays are equal. 1410 | * 1411 | * @param string $expectedJson 1412 | * @param string $actualJson 1413 | * @param string $message 1414 | * @see \Codeception\Module\AbstractAsserts::assertJsonStringEqualsJsonString() 1415 | */ 1416 | public function assertJsonStringEqualsJsonString($expectedJson, $actualJson, $message = "") { 1417 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertJsonStringEqualsJsonString', func_get_args())); 1418 | } 1419 | 1420 | 1421 | /** 1422 | * [!] Method is generated. Documentation taken from corresponding module. 1423 | * 1424 | * Asserts that the generated JSON encoded object and the content of the given file are not equal. 1425 | * 1426 | * @param string $expectedFile 1427 | * @param string $actualJson 1428 | * @param string $message 1429 | * @see \Codeception\Module\AbstractAsserts::assertJsonStringNotEqualsJsonFile() 1430 | */ 1431 | public function assertJsonStringNotEqualsJsonFile($expectedFile, $actualJson, $message = "") { 1432 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertJsonStringNotEqualsJsonFile', func_get_args())); 1433 | } 1434 | 1435 | 1436 | /** 1437 | * [!] Method is generated. Documentation taken from corresponding module. 1438 | * 1439 | * Asserts that two given JSON encoded objects or arrays are not equal. 1440 | * 1441 | * @param string $expectedJson 1442 | * @param string $actualJson 1443 | * @param string $message 1444 | * @see \Codeception\Module\AbstractAsserts::assertJsonStringNotEqualsJsonString() 1445 | */ 1446 | public function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, $message = "") { 1447 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertJsonStringNotEqualsJsonString', func_get_args())); 1448 | } 1449 | 1450 | 1451 | /** 1452 | * [!] Method is generated. Documentation taken from corresponding module. 1453 | * 1454 | * Asserts that a value is smaller than another value. 1455 | * 1456 | * @param $expected 1457 | * @param $actual 1458 | * @param string $message 1459 | * @see \Codeception\Module\AbstractAsserts::assertLessThan() 1460 | */ 1461 | public function assertLessThan($expected, $actual, $message = "") { 1462 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertLessThan', func_get_args())); 1463 | } 1464 | 1465 | 1466 | /** 1467 | * [!] Method is generated. Documentation taken from corresponding module. 1468 | * 1469 | * Asserts that a value is smaller than or equal to another value. 1470 | * 1471 | * @param $expected 1472 | * @param $actual 1473 | * @param string $message 1474 | * @see \Codeception\Module\AbstractAsserts::assertLessThanOrEqual() 1475 | */ 1476 | public function assertLessThanOrEqual($expected, $actual, $message = "") { 1477 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertLessThanOrEqual', func_get_args())); 1478 | } 1479 | 1480 | 1481 | /** 1482 | * [!] Method is generated. Documentation taken from corresponding module. 1483 | * 1484 | * Asserts that a string matches a given regular expression. 1485 | * 1486 | * @param string $pattern 1487 | * @param string $string 1488 | * @param string $message 1489 | * @see \Codeception\Module\AbstractAsserts::assertMatchesRegularExpression() 1490 | */ 1491 | public function assertMatchesRegularExpression($pattern, $string, $message = "") { 1492 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertMatchesRegularExpression', func_get_args())); 1493 | } 1494 | 1495 | 1496 | /** 1497 | * [!] Method is generated. Documentation taken from corresponding module. 1498 | * 1499 | * Asserts that a variable is nan. 1500 | * 1501 | * @param $actual 1502 | * @param string $message 1503 | * @see \Codeception\Module\AbstractAsserts::assertNan() 1504 | */ 1505 | public function assertNan($actual, $message = "") { 1506 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNan', func_get_args())); 1507 | } 1508 | 1509 | 1510 | /** 1511 | * [!] Method is generated. Documentation taken from corresponding module. 1512 | * 1513 | * Asserts that a haystack does not contain a needle. 1514 | * 1515 | * @param $needle 1516 | * @param $haystack 1517 | * @param string $message 1518 | * @see \Codeception\Module\AbstractAsserts::assertNotContains() 1519 | */ 1520 | public function assertNotContains($needle, $haystack, $message = "") { 1521 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotContains', func_get_args())); 1522 | } 1523 | 1524 | 1525 | /** 1526 | * [!] Method is generated. Documentation taken from corresponding module. 1527 | * 1528 | * 1529 | * @see \Codeception\Module\AbstractAsserts::assertNotContainsEquals() 1530 | */ 1531 | public function assertNotContainsEquals($needle, $haystack, $message = "") { 1532 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotContainsEquals', func_get_args())); 1533 | } 1534 | 1535 | 1536 | /** 1537 | * [!] Method is generated. Documentation taken from corresponding module. 1538 | * 1539 | * Asserts that a haystack does not contain only values of a given type. 1540 | * 1541 | * @param string $type 1542 | * @param $haystack 1543 | * @param bool|null $isNativeType 1544 | * @param string $message 1545 | * @see \Codeception\Module\AbstractAsserts::assertNotContainsOnly() 1546 | */ 1547 | public function assertNotContainsOnly($type, $haystack, $isNativeType = NULL, $message = "") { 1548 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotContainsOnly', func_get_args())); 1549 | } 1550 | 1551 | 1552 | /** 1553 | * [!] Method is generated. Documentation taken from corresponding module. 1554 | * 1555 | * Asserts the number of elements of an array, Countable or Traversable. 1556 | * 1557 | * @param int $expectedCount 1558 | * @param Countable|iterable $haystack 1559 | * @param string $message 1560 | * @see \Codeception\Module\AbstractAsserts::assertNotCount() 1561 | */ 1562 | public function assertNotCount($expectedCount, $haystack, $message = "") { 1563 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotCount', func_get_args())); 1564 | } 1565 | 1566 | 1567 | /** 1568 | * [!] Method is generated. Documentation taken from corresponding module. 1569 | * 1570 | * Asserts that a variable is not empty. 1571 | * 1572 | * @param $actual 1573 | * @param string $message 1574 | * @see \Codeception\Module\AbstractAsserts::assertNotEmpty() 1575 | */ 1576 | public function assertNotEmpty($actual, $message = "") { 1577 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotEmpty', func_get_args())); 1578 | } 1579 | 1580 | 1581 | /** 1582 | * [!] Method is generated. Documentation taken from corresponding module. 1583 | * 1584 | * Asserts that two variables are not equal. 1585 | * 1586 | * @param $expected 1587 | * @param $actual 1588 | * @param string $message 1589 | * @see \Codeception\Module\AbstractAsserts::assertNotEquals() 1590 | */ 1591 | public function assertNotEquals($expected, $actual, $message = "") { 1592 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotEquals', func_get_args())); 1593 | } 1594 | 1595 | 1596 | /** 1597 | * [!] Method is generated. Documentation taken from corresponding module. 1598 | * 1599 | * Asserts that two variables are not equal (canonicalizing). 1600 | * 1601 | * @param $expected 1602 | * @param $actual 1603 | * @param string $message 1604 | * @see \Codeception\Module\AbstractAsserts::assertNotEqualsCanonicalizing() 1605 | */ 1606 | public function assertNotEqualsCanonicalizing($expected, $actual, $message = "") { 1607 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotEqualsCanonicalizing', func_get_args())); 1608 | } 1609 | 1610 | 1611 | /** 1612 | * [!] Method is generated. Documentation taken from corresponding module. 1613 | * 1614 | * Asserts that two variables are not equal (ignoring case). 1615 | * 1616 | * @param $expected 1617 | * @param $actual 1618 | * @param string $message 1619 | * @see \Codeception\Module\AbstractAsserts::assertNotEqualsIgnoringCase() 1620 | */ 1621 | public function assertNotEqualsIgnoringCase($expected, $actual, $message = "") { 1622 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotEqualsIgnoringCase', func_get_args())); 1623 | } 1624 | 1625 | 1626 | /** 1627 | * [!] Method is generated. Documentation taken from corresponding module. 1628 | * 1629 | * Asserts that two variables are not equal (with delta). 1630 | * 1631 | * @param $expected 1632 | * @param $actual 1633 | * @param float $delta 1634 | * @param string $message 1635 | * @see \Codeception\Module\AbstractAsserts::assertNotEqualsWithDelta() 1636 | */ 1637 | public function assertNotEqualsWithDelta($expected, $actual, $delta, $message = "") { 1638 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotEqualsWithDelta', func_get_args())); 1639 | } 1640 | 1641 | 1642 | /** 1643 | * [!] Method is generated. Documentation taken from corresponding module. 1644 | * 1645 | * Asserts that a condition is not false. 1646 | * 1647 | * @param $condition 1648 | * @param string $message 1649 | * @see \Codeception\Module\AbstractAsserts::assertNotFalse() 1650 | */ 1651 | public function assertNotFalse($condition, $message = "") { 1652 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotFalse', func_get_args())); 1653 | } 1654 | 1655 | 1656 | /** 1657 | * [!] Method is generated. Documentation taken from corresponding module. 1658 | * 1659 | * Asserts that a variable is not of a given type. 1660 | * 1661 | * @param $expected 1662 | * @param $actual 1663 | * @param string $message 1664 | * @see \Codeception\Module\AbstractAsserts::assertNotInstanceOf() 1665 | */ 1666 | public function assertNotInstanceOf($expected, $actual, $message = "") { 1667 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotInstanceOf', func_get_args())); 1668 | } 1669 | 1670 | 1671 | /** 1672 | * [!] Method is generated. Documentation taken from corresponding module. 1673 | * 1674 | * Asserts that a variable is not null. 1675 | * 1676 | * @param $actual 1677 | * @param string $message 1678 | * @see \Codeception\Module\AbstractAsserts::assertNotNull() 1679 | */ 1680 | public function assertNotNull($actual, $message = "") { 1681 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotNull', func_get_args())); 1682 | } 1683 | 1684 | 1685 | /** 1686 | * [!] Method is generated. Documentation taken from corresponding module. 1687 | * 1688 | * Asserts that two variables do not have the same type and value. 1689 | * 1690 | * @param $expected 1691 | * @param $actual 1692 | * @param string $message 1693 | * @see \Codeception\Module\AbstractAsserts::assertNotSame() 1694 | */ 1695 | public function assertNotSame($expected, $actual, $message = "") { 1696 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotSame', func_get_args())); 1697 | } 1698 | 1699 | 1700 | /** 1701 | * [!] Method is generated. Documentation taken from corresponding module. 1702 | * 1703 | * Assert that the size of two arrays (or `Countable` or `Traversable` objects) is not the same. 1704 | * 1705 | * @param Countable|iterable $expected 1706 | * @param Countable|iterable $actual 1707 | * @param string $message 1708 | * @see \Codeception\Module\AbstractAsserts::assertNotSameSize() 1709 | */ 1710 | public function assertNotSameSize($expected, $actual, $message = "") { 1711 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotSameSize', func_get_args())); 1712 | } 1713 | 1714 | 1715 | /** 1716 | * [!] Method is generated. Documentation taken from corresponding module. 1717 | * 1718 | * Asserts that a condition is not true. 1719 | * 1720 | * @param $condition 1721 | * @param string $message 1722 | * @see \Codeception\Module\AbstractAsserts::assertNotTrue() 1723 | */ 1724 | public function assertNotTrue($condition, $message = "") { 1725 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotTrue', func_get_args())); 1726 | } 1727 | 1728 | 1729 | /** 1730 | * [!] Method is generated. Documentation taken from corresponding module. 1731 | * 1732 | * Asserts that a variable is null. 1733 | * 1734 | * @param $actual 1735 | * @param string $message 1736 | * @see \Codeception\Module\AbstractAsserts::assertNull() 1737 | */ 1738 | public function assertNull($actual, $message = "") { 1739 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNull', func_get_args())); 1740 | } 1741 | 1742 | 1743 | /** 1744 | * [!] Method is generated. Documentation taken from corresponding module. 1745 | * 1746 | * Asserts that an object has a specified attribute. 1747 | * 1748 | * @param string $attributeName 1749 | * @param object $object 1750 | * @param string $message 1751 | * @see \Codeception\Module\AbstractAsserts::assertObjectHasAttribute() 1752 | */ 1753 | public function assertObjectHasAttribute($attributeName, $object, $message = "") { 1754 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertObjectHasAttribute', func_get_args())); 1755 | } 1756 | 1757 | 1758 | /** 1759 | * [!] Method is generated. Documentation taken from corresponding module. 1760 | * 1761 | * Asserts that an object does not have a specified attribute. 1762 | * 1763 | * @param string $attributeName 1764 | * @param object $object 1765 | * @param string $message 1766 | * @see \Codeception\Module\AbstractAsserts::assertObjectNotHasAttribute() 1767 | */ 1768 | public function assertObjectNotHasAttribute($attributeName, $object, $message = "") { 1769 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertObjectNotHasAttribute', func_get_args())); 1770 | } 1771 | 1772 | 1773 | /** 1774 | * [!] Method is generated. Documentation taken from corresponding module. 1775 | * 1776 | * Asserts that two variables have the same type and value. 1777 | * 1778 | * @param $expected 1779 | * @param $actual 1780 | * @param string $message 1781 | * @see \Codeception\Module\AbstractAsserts::assertSame() 1782 | */ 1783 | public function assertSame($expected, $actual, $message = "") { 1784 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertSame', func_get_args())); 1785 | } 1786 | 1787 | 1788 | /** 1789 | * [!] Method is generated. Documentation taken from corresponding module. 1790 | * 1791 | * Assert that the size of two arrays (or `Countable` or `Traversable` objects) is the same. 1792 | * 1793 | * @param Countable|iterable $expected 1794 | * @param Countable|iterable $actual 1795 | * @param string $message 1796 | * @see \Codeception\Module\AbstractAsserts::assertSameSize() 1797 | */ 1798 | public function assertSameSize($expected, $actual, $message = "") { 1799 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertSameSize', func_get_args())); 1800 | } 1801 | 1802 | 1803 | /** 1804 | * [!] Method is generated. Documentation taken from corresponding module. 1805 | * 1806 | * @param string $needle 1807 | * @param string $haystack 1808 | * @param string $message 1809 | * @see \Codeception\Module\AbstractAsserts::assertStringContainsString() 1810 | */ 1811 | public function assertStringContainsString($needle, $haystack, $message = "") { 1812 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringContainsString', func_get_args())); 1813 | } 1814 | 1815 | 1816 | /** 1817 | * [!] Method is generated. Documentation taken from corresponding module. 1818 | * 1819 | * 1820 | * @see \Codeception\Module\AbstractAsserts::assertStringContainsStringIgnoringCase() 1821 | */ 1822 | public function assertStringContainsStringIgnoringCase($needle, $haystack, $message = "") { 1823 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringContainsStringIgnoringCase', func_get_args())); 1824 | } 1825 | 1826 | 1827 | /** 1828 | * [!] Method is generated. Documentation taken from corresponding module. 1829 | * 1830 | * Asserts that a string ends not with a given suffix. 1831 | * 1832 | * @param string $suffix 1833 | * @param string $string 1834 | * @param string $message 1835 | * @see \Codeception\Module\AbstractAsserts::assertStringEndsNotWith() 1836 | */ 1837 | public function assertStringEndsNotWith($suffix, $string, $message = "") { 1838 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringEndsNotWith', func_get_args())); 1839 | } 1840 | 1841 | 1842 | /** 1843 | * [!] Method is generated. Documentation taken from corresponding module. 1844 | * 1845 | * Asserts that a string ends with a given suffix. 1846 | * 1847 | * @param string $suffix 1848 | * @param string $string 1849 | * @param string $message 1850 | * @see \Codeception\Module\AbstractAsserts::assertStringEndsWith() 1851 | */ 1852 | public function assertStringEndsWith($suffix, $string, $message = "") { 1853 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringEndsWith', func_get_args())); 1854 | } 1855 | 1856 | 1857 | /** 1858 | * [!] Method is generated. Documentation taken from corresponding module. 1859 | * 1860 | * Asserts that the contents of a string is equal to the contents of a file. 1861 | * 1862 | * @param string $expectedFile 1863 | * @param string $actualString 1864 | * @param string $message 1865 | * @see \Codeception\Module\AbstractAsserts::assertStringEqualsFile() 1866 | */ 1867 | public function assertStringEqualsFile($expectedFile, $actualString, $message = "") { 1868 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringEqualsFile', func_get_args())); 1869 | } 1870 | 1871 | 1872 | /** 1873 | * [!] Method is generated. Documentation taken from corresponding module. 1874 | * 1875 | * Asserts that the contents of a string is equal to the contents of a file (canonicalizing). 1876 | * 1877 | * @param string $expectedFile 1878 | * @param string $actualString 1879 | * @param string $message 1880 | * @see \Codeception\Module\AbstractAsserts::assertStringEqualsFileCanonicalizing() 1881 | */ 1882 | public function assertStringEqualsFileCanonicalizing($expectedFile, $actualString, $message = "") { 1883 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringEqualsFileCanonicalizing', func_get_args())); 1884 | } 1885 | 1886 | 1887 | /** 1888 | * [!] Method is generated. Documentation taken from corresponding module. 1889 | * 1890 | * Asserts that the contents of a string is equal to the contents of a file (ignoring case). 1891 | * 1892 | * @param string $expectedFile 1893 | * @param string $actualString 1894 | * @param string $message 1895 | * @see \Codeception\Module\AbstractAsserts::assertStringEqualsFileIgnoringCase() 1896 | */ 1897 | public function assertStringEqualsFileIgnoringCase($expectedFile, $actualString, $message = "") { 1898 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringEqualsFileIgnoringCase', func_get_args())); 1899 | } 1900 | 1901 | 1902 | /** 1903 | * [!] Method is generated. Documentation taken from corresponding module. 1904 | * 1905 | * Asserts that a string matches a given format string. 1906 | * 1907 | * @param string $format 1908 | * @param string $string 1909 | * @param string $message 1910 | * @see \Codeception\Module\AbstractAsserts::assertStringMatchesFormat() 1911 | */ 1912 | public function assertStringMatchesFormat($format, $string, $message = "") { 1913 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringMatchesFormat', func_get_args())); 1914 | } 1915 | 1916 | 1917 | /** 1918 | * [!] Method is generated. Documentation taken from corresponding module. 1919 | * 1920 | * Asserts that a string matches a given format file. 1921 | * 1922 | * @param string $formatFile 1923 | * @param string $string 1924 | * @param string $message 1925 | * @see \Codeception\Module\AbstractAsserts::assertStringMatchesFormatFile() 1926 | */ 1927 | public function assertStringMatchesFormatFile($formatFile, $string, $message = "") { 1928 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringMatchesFormatFile', func_get_args())); 1929 | } 1930 | 1931 | 1932 | /** 1933 | * [!] Method is generated. Documentation taken from corresponding module. 1934 | * 1935 | * @param string $needle 1936 | * @param string $haystack 1937 | * @param string $message 1938 | * @see \Codeception\Module\AbstractAsserts::assertStringNotContainsString() 1939 | */ 1940 | public function assertStringNotContainsString($needle, $haystack, $message = "") { 1941 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringNotContainsString', func_get_args())); 1942 | } 1943 | 1944 | 1945 | /** 1946 | * [!] Method is generated. Documentation taken from corresponding module. 1947 | * 1948 | * @param string $needle 1949 | * @param string $haystack 1950 | * @param string $message 1951 | * @see \Codeception\Module\AbstractAsserts::assertStringNotContainsStringIgnoringCase() 1952 | */ 1953 | public function assertStringNotContainsStringIgnoringCase($needle, $haystack, $message = "") { 1954 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringNotContainsStringIgnoringCase', func_get_args())); 1955 | } 1956 | 1957 | 1958 | /** 1959 | * [!] Method is generated. Documentation taken from corresponding module. 1960 | * 1961 | * Asserts that the contents of a string is not equal to the contents of a file. 1962 | * 1963 | * @param string $expectedFile 1964 | * @param string $actualString 1965 | * @param string $message 1966 | * @see \Codeception\Module\AbstractAsserts::assertStringNotEqualsFile() 1967 | */ 1968 | public function assertStringNotEqualsFile($expectedFile, $actualString, $message = "") { 1969 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringNotEqualsFile', func_get_args())); 1970 | } 1971 | 1972 | 1973 | /** 1974 | * [!] Method is generated. Documentation taken from corresponding module. 1975 | * 1976 | * Asserts that the contents of a string is not equal to the contents of a file (canonicalizing). 1977 | * @param string $expectedFile 1978 | * @param string $actualString 1979 | * @param string $message 1980 | * @see \Codeception\Module\AbstractAsserts::assertStringNotEqualsFileCanonicalizing() 1981 | */ 1982 | public function assertStringNotEqualsFileCanonicalizing($expectedFile, $actualString, $message = "") { 1983 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringNotEqualsFileCanonicalizing', func_get_args())); 1984 | } 1985 | 1986 | 1987 | /** 1988 | * [!] Method is generated. Documentation taken from corresponding module. 1989 | * 1990 | * Asserts that the contents of a string is not equal to the contents of a file (ignoring case). 1991 | * 1992 | * @param string $expectedFile 1993 | * @param string $actualString 1994 | * @param string $message 1995 | * @see \Codeception\Module\AbstractAsserts::assertStringNotEqualsFileIgnoringCase() 1996 | */ 1997 | public function assertStringNotEqualsFileIgnoringCase($expectedFile, $actualString, $message = "") { 1998 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringNotEqualsFileIgnoringCase', func_get_args())); 1999 | } 2000 | 2001 | 2002 | /** 2003 | * [!] Method is generated. Documentation taken from corresponding module. 2004 | * 2005 | * Asserts that a string does not match a given format string. 2006 | * 2007 | * @param string $format 2008 | * @param string $string 2009 | * @param string $message 2010 | * @see \Codeception\Module\AbstractAsserts::assertStringNotMatchesFormat() 2011 | */ 2012 | public function assertStringNotMatchesFormat($format, $string, $message = "") { 2013 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringNotMatchesFormat', func_get_args())); 2014 | } 2015 | 2016 | 2017 | /** 2018 | * [!] Method is generated. Documentation taken from corresponding module. 2019 | * 2020 | * Asserts that a string does not match a given format string. 2021 | * 2022 | * @param string $formatFile 2023 | * @param string $string 2024 | * @param string $message 2025 | * @see \Codeception\Module\AbstractAsserts::assertStringNotMatchesFormatFile() 2026 | */ 2027 | public function assertStringNotMatchesFormatFile($formatFile, $string, $message = "") { 2028 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringNotMatchesFormatFile', func_get_args())); 2029 | } 2030 | 2031 | 2032 | /** 2033 | * [!] Method is generated. Documentation taken from corresponding module. 2034 | * 2035 | * Asserts that a string starts not with a given prefix. 2036 | * 2037 | * @param string $prefix 2038 | * @param string $string 2039 | * @param string $message 2040 | * @see \Codeception\Module\AbstractAsserts::assertStringStartsNotWith() 2041 | */ 2042 | public function assertStringStartsNotWith($prefix, $string, $message = "") { 2043 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringStartsNotWith', func_get_args())); 2044 | } 2045 | 2046 | 2047 | /** 2048 | * [!] Method is generated. Documentation taken from corresponding module. 2049 | * 2050 | * Asserts that a string starts with a given prefix. 2051 | * 2052 | * @param string $prefix 2053 | * @param string $string 2054 | * @param string $message 2055 | * @see \Codeception\Module\AbstractAsserts::assertStringStartsWith() 2056 | */ 2057 | public function assertStringStartsWith($prefix, $string, $message = "") { 2058 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringStartsWith', func_get_args())); 2059 | } 2060 | 2061 | 2062 | /** 2063 | * [!] Method is generated. Documentation taken from corresponding module. 2064 | * 2065 | * Evaluates a PHPUnit\Framework\Constraint matcher object. 2066 | * 2067 | * @param $value 2068 | * @param Constraint $constraint 2069 | * @param string $message 2070 | * @see \Codeception\Module\AbstractAsserts::assertThat() 2071 | */ 2072 | public function assertThat($value, $constraint, $message = "") { 2073 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertThat', func_get_args())); 2074 | } 2075 | 2076 | 2077 | /** 2078 | * [!] Method is generated. Documentation taken from corresponding module. 2079 | * 2080 | * Asserts that a condition is true. 2081 | * 2082 | * @param $condition 2083 | * @param string $message 2084 | * @see \Codeception\Module\AbstractAsserts::assertTrue() 2085 | */ 2086 | public function assertTrue($condition, $message = "") { 2087 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertTrue', func_get_args())); 2088 | } 2089 | 2090 | 2091 | /** 2092 | * [!] Method is generated. Documentation taken from corresponding module. 2093 | * 2094 | * Asserts that two XML files are equal. 2095 | * 2096 | * @param string $expectedFile 2097 | * @param string $actualFile 2098 | * @param string $message 2099 | * @see \Codeception\Module\AbstractAsserts::assertXmlFileEqualsXmlFile() 2100 | */ 2101 | public function assertXmlFileEqualsXmlFile($expectedFile, $actualFile, $message = "") { 2102 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertXmlFileEqualsXmlFile', func_get_args())); 2103 | } 2104 | 2105 | 2106 | /** 2107 | * [!] Method is generated. Documentation taken from corresponding module. 2108 | * 2109 | * Asserts that two XML files are not equal. 2110 | * 2111 | * @param string $expectedFile 2112 | * @param string $actualFile 2113 | * @param string $message 2114 | * @see \Codeception\Module\AbstractAsserts::assertXmlFileNotEqualsXmlFile() 2115 | */ 2116 | public function assertXmlFileNotEqualsXmlFile($expectedFile, $actualFile, $message = "") { 2117 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertXmlFileNotEqualsXmlFile', func_get_args())); 2118 | } 2119 | 2120 | 2121 | /** 2122 | * [!] Method is generated. Documentation taken from corresponding module. 2123 | * 2124 | * Asserts that two XML documents are equal. 2125 | * 2126 | * @param string $expectedFile 2127 | * @param DOMDocument|string $actualXml 2128 | * @param string $message 2129 | * @see \Codeception\Module\AbstractAsserts::assertXmlStringEqualsXmlFile() 2130 | */ 2131 | public function assertXmlStringEqualsXmlFile($expectedFile, $actualXml, $message = "") { 2132 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertXmlStringEqualsXmlFile', func_get_args())); 2133 | } 2134 | 2135 | 2136 | /** 2137 | * [!] Method is generated. Documentation taken from corresponding module. 2138 | * 2139 | * Asserts that two XML documents are equal. 2140 | * 2141 | * @param DOMDocument|string $expectedXml 2142 | * @param DOMDocument|string $actualXml 2143 | * @param string $message 2144 | * @see \Codeception\Module\AbstractAsserts::assertXmlStringEqualsXmlString() 2145 | */ 2146 | public function assertXmlStringEqualsXmlString($expectedXml, $actualXml, $message = "") { 2147 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertXmlStringEqualsXmlString', func_get_args())); 2148 | } 2149 | 2150 | 2151 | /** 2152 | * [!] Method is generated. Documentation taken from corresponding module. 2153 | * 2154 | * Asserts that two XML documents are not equal. 2155 | * 2156 | * @param string $expectedFile 2157 | * @param DOMDocument|string $actualXml 2158 | * @param string $message 2159 | * @see \Codeception\Module\AbstractAsserts::assertXmlStringNotEqualsXmlFile() 2160 | */ 2161 | public function assertXmlStringNotEqualsXmlFile($expectedFile, $actualXml, $message = "") { 2162 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertXmlStringNotEqualsXmlFile', func_get_args())); 2163 | } 2164 | 2165 | 2166 | /** 2167 | * [!] Method is generated. Documentation taken from corresponding module. 2168 | * 2169 | * Asserts that two XML documents are not equal. 2170 | * 2171 | * @param DOMDocument|string $expectedXml 2172 | * @param DOMDocument|string $actualXml 2173 | * @param string $message 2174 | * @see \Codeception\Module\AbstractAsserts::assertXmlStringNotEqualsXmlString() 2175 | */ 2176 | public function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, $message = "") { 2177 | return $this->getScenario()->runStep(new \Codeception\Step\Action('assertXmlStringNotEqualsXmlString', func_get_args())); 2178 | } 2179 | 2180 | 2181 | /** 2182 | * [!] Method is generated. Documentation taken from corresponding module. 2183 | * 2184 | * Fails a test with the given message. 2185 | * 2186 | * @param string $message 2187 | * @see \Codeception\Module\AbstractAsserts::fail() 2188 | */ 2189 | public function fail($message = "") { 2190 | return $this->getScenario()->runStep(new \Codeception\Step\Action('fail', func_get_args())); 2191 | } 2192 | 2193 | 2194 | /** 2195 | * [!] Method is generated. Documentation taken from corresponding module. 2196 | * 2197 | * Mark the test as incomplete. 2198 | * 2199 | * @param string $message 2200 | * @see \Codeception\Module\AbstractAsserts::markTestIncomplete() 2201 | */ 2202 | public function markTestIncomplete($message = "") { 2203 | return $this->getScenario()->runStep(new \Codeception\Step\Action('markTestIncomplete', func_get_args())); 2204 | } 2205 | 2206 | 2207 | /** 2208 | * [!] Method is generated. Documentation taken from corresponding module. 2209 | * 2210 | * Mark the test as skipped. 2211 | * 2212 | * @param string $message 2213 | * @see \Codeception\Module\AbstractAsserts::markTestSkipped() 2214 | */ 2215 | public function markTestSkipped($message = "") { 2216 | return $this->getScenario()->runStep(new \Codeception\Step\Action('markTestSkipped', func_get_args())); 2217 | } 2218 | } 2219 | -------------------------------------------------------------------------------- /tests/config.php: -------------------------------------------------------------------------------- 1 | 'wpn-tests', 7 | 'basePath' => dirname(__DIR__), 8 | 'bootstrap' => ['wpn'], 9 | 'components' => [ 10 | 'db' => [ 11 | 'class' => Connection::class, 12 | 'dsn' => 'mysql:host=127.0.0.1;dbname=wpn_test_db;', 13 | 'username' => 'root', 14 | 'password' => 'password', 15 | 'enableSchemaCache' => true, 16 | 'charset' => 'latin1', 17 | ], 18 | ], 19 | 'modules' => [ 20 | 'wpn' => [ 21 | 'class' => \machour\yii2\wpn\Module::class, 22 | 'components' => [ 23 | 'pusher' => [ 24 | 'class' => \machour\yii2\wpn\components\Pusher::class, 25 | ] 26 | ] 27 | ] 28 | ] 29 | ]; -------------------------------------------------------------------------------- /tests/fixtures/WpnAppFixture.php: -------------------------------------------------------------------------------- 1 | 1, 6 | 'name' => 'Valid App', 7 | 'host' => 'localhost', 8 | 'subject' => 'mailto:contact@example.com', 9 | 'public_key' => 'BPNOgWF76BVzSYpRWDWV3SVc9wvZgfvImhhSQNLYGRs_Zfy5rEXb5NITVoUzbEoe9E85oO2830ftuZfwjmHr0FE', 10 | 'private_key' => 'KRbzYHPtD54HPyXP6bkqavNUiTGKls8C0rLyAk7fCoU', 11 | 'enabled' => true, 12 | 'created_at' => date('Y-m-d H:i:s'), 13 | 'updated_at' => date('Y-m-d H:i:s'), 14 | ], 15 | [ 16 | 'id' => 2, 17 | 'name' => 'Disabled App', 18 | 'host' => 'localhost2', 19 | 'subject' => 'mailto:contact@example.com', 20 | 'public_key' => 'BPNOgWF76BVzSYpRWDWV3SVc9wvZgfvImhhSQNLYGRs_Zfy5rEXb5NITVoUzbEoe9E85oO2830ftuZfwjmHr0FE', 21 | 'private_key' => 'KRbzYHPtD54HPyXP6bkqavNUiTGKls8C0rLyAk7fCoU', 22 | 'enabled' => false, 23 | 'created_at' => date('Y-m-d H:i:s'), 24 | 'updated_at' => date('Y-m-d H:i:s'), 25 | ], 26 | [ 27 | 'id' => 3, 28 | 'name' => 'Valid App with icon', 29 | 'host' => 'localhost3', 30 | 'subject' => 'mailto:contact@example.com', 31 | 'public_key' => 'BPNOgWF76BVzSYpRWDWV3SVc9wvZgfvImhhSQNLYGRs_Zfy5rEXb5NITVoUzbEoe9E85oO2830ftuZfwjmHr0FE', 32 | 'private_key' => 'KRbzYHPtD54HPyXP6bkqavNUiTGKls8C0rLyAk7fCoU', 33 | 'icon' => 'https://www.yiiframework.com/image/design/logo/yii3_sign.png', 34 | 'enabled' => false, 35 | 'created_at' => date('Y-m-d H:i:s'), 36 | 'updated_at' => date('Y-m-d H:i:s'), 37 | ], 38 | ]; -------------------------------------------------------------------------------- /tests/fixtures/data/wpn_campaign.php: -------------------------------------------------------------------------------- 1 | 1, 6 | 'app_id' => 1, 7 | 'title' => 'Push on valid application', 8 | 'tag' => 'push-1', 9 | 'body' => 'My push body', 10 | 'created_at' => date('Y-m-d H:i:s'), 11 | 'scheduled_at' => date('Y-m-d H:i:s'), 12 | 'updated_at' => date('Y-m-d H:i:s'), 13 | ], 14 | [ 15 | 'id' => 2, 16 | 'app_id' => 2, 17 | 'title' => 'Push on invalid application', 18 | 'tag' => 'push-2', 19 | 'body' => 'My push body', 20 | 'created_at' => date('Y-m-d H:i:s'), 21 | 'scheduled_at' => date('Y-m-d H:i:s'), 22 | 'updated_at' => date('Y-m-d H:i:s'), 23 | ], 24 | [ 25 | 'id' => 3, 26 | 'app_id' => 3, 27 | 'title' => 'Push on application with icon', 28 | 'tag' => 'push-3', 29 | 'body' => 'My push body', 30 | 'created_at' => date('Y-m-d H:i:s'), 31 | 'scheduled_at' => date('Y-m-d H:i:s'), 32 | 'updated_at' => date('Y-m-d H:i:s'), 33 | ], 34 | [ 35 | 'id' => 4, 36 | 'app_id' => 1, 37 | 'title' => 'Push for test users only', 38 | 'tag' => 'push-test', 39 | 'body' => 'My test push body', 40 | 'created_at' => date('Y-m-d H:i:s'), 41 | 'scheduled_at' => date('Y-m-d H:i:s'), 42 | 'updated_at' => date('Y-m-d H:i:s'), 43 | ], 44 | ]; -------------------------------------------------------------------------------- /tests/fixtures/data/wpn_subscription.php: -------------------------------------------------------------------------------- 1 | 1, 6 | 'endpoint' => 'endpoint-subscribed-1', 7 | 'auth' => 'auth-1', 8 | 'public_key' => 'public-1', 9 | 'content_encoding' => 'encoding-1', 10 | 'subscribed' => 1, 11 | 'test_user' => 0, 12 | 'app_id' => 1, 13 | 'ip' => '127.0.0.1', 14 | 'created_at' => date('Y-m-d H:i:s'), 15 | 'updated_at' => date('Y-m-d H:i:s'), 16 | ], 17 | [ 18 | 'id' => 2, 19 | 'endpoint' => 'endpoint-subscribed-and-test-2', 20 | 'auth' => 'auth-2', 21 | 'public_key' => 'public-2', 22 | 'content_encoding' => 'encoding-2', 23 | 'subscribed' => 1, 24 | 'test_user' => 1, 25 | 'app_id' => 1, 26 | 'ip' => '127.0.0.1', 27 | 'created_at' => date('Y-m-d H:i:s'), 28 | 'updated_at' => date('Y-m-d H:i:s'), 29 | ], 30 | [ 31 | 'id' => 3, 32 | 'endpoint' => 'endpoint-not-subscribed-3', 33 | 'auth' => 'auth-3', 34 | 'public_key' => 'public-3', 35 | 'content_encoding' => 'encoding-3', 36 | 'subscribed' => 0, 37 | 'test_user' => 0, 38 | 'app_id' => 1, 39 | 'ip' => '127.0.0.1', 40 | 'created_at' => date('Y-m-d H:i:s'), 41 | 'updated_at' => date('Y-m-d H:i:s'), 42 | ], 43 | 44 | ]; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": [ 3 | "dom", 4 | "es5", 5 | "scripthost", 6 | "es2015.promise" 7 | ], 8 | "compilerOptions": { 9 | "target": "es5", 10 | "module": "commonjs", 11 | "esModuleInterop": true, 12 | "experimentalDecorators": true, 13 | "sourceMap": true, 14 | "noEmitOnError": false, 15 | "lib": [ 16 | "dom", 17 | "es5", 18 | "scripthost", 19 | "es2015.promise" 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /yii_test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 21 | exit($exitCode); 22 | --------------------------------------------------------------------------------