first commit
This commit is contained in:
0
705ac31070b0818df977b627c0d01f2afe04dfea
Normal file
0
705ac31070b0818df977b627c0d01f2afe04dfea
Normal file
29
LICENSE.md
Normal file
29
LICENSE.md
Normal file
@ -0,0 +1,29 @@
|
||||
Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Yii Software LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
233
README.md
Normal file
233
README.md
Normal file
@ -0,0 +1,233 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/yiisoft" target="_blank">
|
||||
<img src="https://avatars0.githubusercontent.com/u/993323" height="100px">
|
||||
</a>
|
||||
<h1 align="center">Yii 2 Basic Project Template</h1>
|
||||
<br>
|
||||
</p>
|
||||
|
||||
Yii 2 Basic Project Template is a skeleton [Yii 2](https://www.yiiframework.com/) application best for
|
||||
rapidly creating small projects.
|
||||
|
||||
The template contains the basic features including user login/logout and a contact page.
|
||||
It includes all commonly used configurations that would allow you to focus on adding new
|
||||
features to your application.
|
||||
|
||||
[](https://packagist.org/packages/yiisoft/yii2-app-basic)
|
||||
[](https://packagist.org/packages/yiisoft/yii2-app-basic)
|
||||
[](https://github.com/yiisoft/yii2-app-basic/actions?query=workflow%3Abuild)
|
||||
|
||||
DIRECTORY STRUCTURE
|
||||
-------------------
|
||||
|
||||
assets/ contains assets definition
|
||||
commands/ contains console commands (controllers)
|
||||
config/ contains application configurations
|
||||
controllers/ contains Web controller classes
|
||||
mail/ contains view files for e-mails
|
||||
models/ contains model classes
|
||||
runtime/ contains files generated during runtime
|
||||
tests/ contains various tests for the basic application
|
||||
vendor/ contains dependent 3rd-party packages
|
||||
views/ contains view files for the Web application
|
||||
web/ contains the entry script and Web resources
|
||||
|
||||
|
||||
|
||||
REQUIREMENTS
|
||||
------------
|
||||
|
||||
The minimum requirement by this project template that your Web server supports PHP 7.4.
|
||||
|
||||
|
||||
INSTALLATION
|
||||
------------
|
||||
|
||||
### Install via Composer
|
||||
|
||||
If you do not have [Composer](https://getcomposer.org/), you may install it by following the instructions
|
||||
at [getcomposer.org](https://getcomposer.org/doc/00-intro.md#installation-nix).
|
||||
|
||||
You can then install this project template using the following command:
|
||||
|
||||
~~~
|
||||
composer create-project --prefer-dist yiisoft/yii2-app-basic basic
|
||||
~~~
|
||||
|
||||
Now you should be able to access the application through the following URL, assuming `basic` is the directory
|
||||
directly under the Web root.
|
||||
|
||||
~~~
|
||||
http://localhost/basic/web/
|
||||
~~~
|
||||
|
||||
### Install from an Archive File
|
||||
|
||||
Extract the archive file downloaded from [yiiframework.com](https://www.yiiframework.com/download/) to
|
||||
a directory named `basic` that is directly under the Web root.
|
||||
|
||||
Set cookie validation key in `config/web.php` file to some random secret string:
|
||||
|
||||
```php
|
||||
'request' => [
|
||||
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
|
||||
'cookieValidationKey' => '<secret random string goes here>',
|
||||
],
|
||||
```
|
||||
|
||||
You can then access the application through the following URL:
|
||||
|
||||
~~~
|
||||
http://localhost/basic/web/
|
||||
~~~
|
||||
|
||||
|
||||
### Install with Docker
|
||||
|
||||
Update your vendor packages
|
||||
|
||||
docker-compose run --rm php composer update --prefer-dist
|
||||
|
||||
Run the installation triggers (creating cookie validation code)
|
||||
|
||||
docker-compose run --rm php composer install
|
||||
|
||||
Start the container
|
||||
|
||||
docker-compose up -d
|
||||
|
||||
You can then access the application through the following URL:
|
||||
|
||||
http://127.0.0.1:8000
|
||||
|
||||
**NOTES:**
|
||||
- Minimum required Docker engine version `17.04` for development (see [Performance tuning for volume mounts](https://docs.docker.com/docker-for-mac/osxfs-caching/))
|
||||
- The default configuration uses a host-volume in your home directory `.docker-composer` for composer caches
|
||||
|
||||
|
||||
CONFIGURATION
|
||||
-------------
|
||||
|
||||
### Database
|
||||
|
||||
Edit the file `config/db.php` with real data, for example:
|
||||
|
||||
```php
|
||||
return [
|
||||
'class' => 'yii\db\Connection',
|
||||
'dsn' => 'mysql:host=localhost;dbname=yii2basic',
|
||||
'username' => 'root',
|
||||
'password' => '1234',
|
||||
'charset' => 'utf8',
|
||||
];
|
||||
```
|
||||
|
||||
**NOTES:**
|
||||
- Yii won't create the database for you, this has to be done manually before you can access it.
|
||||
- Check and edit the other files in the `config/` directory to customize your application as required.
|
||||
- Refer to the README in the `tests` directory for information specific to basic application tests.
|
||||
|
||||
|
||||
TESTING
|
||||
-------
|
||||
|
||||
Tests are located in `tests` directory. They are developed with [Codeception PHP Testing Framework](https://codeception.com/).
|
||||
By default, there are 3 test suites:
|
||||
|
||||
- `unit`
|
||||
- `functional`
|
||||
- `acceptance`
|
||||
|
||||
Tests can be executed by running
|
||||
|
||||
```
|
||||
vendor/bin/codecept run
|
||||
```
|
||||
|
||||
The command above will execute unit and functional tests. Unit tests are testing the system components, while functional
|
||||
tests are for testing user interaction. Acceptance tests are disabled by default as they require additional setup since
|
||||
they perform testing in real browser.
|
||||
|
||||
|
||||
### Running acceptance tests
|
||||
|
||||
To execute acceptance tests do the following:
|
||||
|
||||
1. Rename `tests/acceptance.suite.yml.example` to `tests/acceptance.suite.yml` to enable suite configuration
|
||||
|
||||
2. Replace `codeception/base` package in `composer.json` with `codeception/codeception` to install full-featured
|
||||
version of Codeception
|
||||
|
||||
3. Update dependencies with Composer
|
||||
|
||||
```
|
||||
composer update
|
||||
```
|
||||
|
||||
4. Download [Selenium Server](https://www.seleniumhq.org/download/) and launch it:
|
||||
|
||||
```
|
||||
java -jar ~/selenium-server-standalone-x.xx.x.jar
|
||||
```
|
||||
|
||||
In case of using Selenium Server 3.0 with Firefox browser since v48 or Google Chrome since v53 you must download [GeckoDriver](https://github.com/mozilla/geckodriver/releases) or [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/downloads) and launch Selenium with it:
|
||||
|
||||
```
|
||||
# for Firefox
|
||||
java -jar -Dwebdriver.gecko.driver=~/geckodriver ~/selenium-server-standalone-3.xx.x.jar
|
||||
|
||||
# for Google Chrome
|
||||
java -jar -Dwebdriver.chrome.driver=~/chromedriver ~/selenium-server-standalone-3.xx.x.jar
|
||||
```
|
||||
|
||||
As an alternative way you can use already configured Docker container with older versions of Selenium and Firefox:
|
||||
|
||||
```
|
||||
docker run --net=host selenium/standalone-firefox:2.53.0
|
||||
```
|
||||
|
||||
5. (Optional) Create `yii2basic_test` database and update it by applying migrations if you have them.
|
||||
|
||||
```
|
||||
tests/bin/yii migrate
|
||||
```
|
||||
|
||||
The database configuration can be found at `config/test_db.php`.
|
||||
|
||||
|
||||
6. Start web server:
|
||||
|
||||
```
|
||||
tests/bin/yii serve
|
||||
```
|
||||
|
||||
7. Now you can run all available tests
|
||||
|
||||
```
|
||||
# run all available tests
|
||||
vendor/bin/codecept run
|
||||
|
||||
# run acceptance tests
|
||||
vendor/bin/codecept run acceptance
|
||||
|
||||
# run only unit and functional tests
|
||||
vendor/bin/codecept run unit,functional
|
||||
```
|
||||
|
||||
### Code coverage support
|
||||
|
||||
By default, code coverage is disabled in `codeception.yml` configuration file, you should uncomment needed rows to be able
|
||||
to collect code coverage. You can run your tests and collect coverage with the following command:
|
||||
|
||||
```
|
||||
#collect coverage for all tests
|
||||
vendor/bin/codecept run --coverage --coverage-html --coverage-xml
|
||||
|
||||
#collect coverage only for unit tests
|
||||
vendor/bin/codecept run unit --coverage --coverage-html --coverage-xml
|
||||
|
||||
#collect coverage for unit and functional tests
|
||||
vendor/bin/codecept run functional,unit --coverage --coverage-html --coverage-xml
|
||||
```
|
||||
|
||||
You can see code coverage output under the `tests/_output` directory.
|
||||
163
Serializers/Serializer.php
Normal file
163
Serializers/Serializer.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace app\serializers;
|
||||
|
||||
class Serializer
|
||||
{
|
||||
/* ========= 规则存储 ========= */
|
||||
|
||||
protected array $handlerTrie = [];
|
||||
protected array $removeTrie = [];
|
||||
protected array $appendRules = [];
|
||||
|
||||
public function __construct(
|
||||
protected array|null $source = null
|
||||
) {}
|
||||
|
||||
/* ========= API ========= */
|
||||
|
||||
public function handle(string $path, callable $fn): static
|
||||
{
|
||||
$this->insertTrie($this->handlerTrie, explode('.', $path), $fn);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function remove(string $path): static
|
||||
{
|
||||
$this->insertTrie($this->removeTrie, explode('.', $path), true);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点级追加(path 指向节点)
|
||||
*/
|
||||
public function append(string $path, callable $fn): static
|
||||
{
|
||||
$this->appendRules[] = [
|
||||
'path' => explode('.', $path),
|
||||
'fn' => $fn,
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/* ========= 执行 ========= */
|
||||
|
||||
public function serialize(): array
|
||||
{
|
||||
$data = $this->source ?? [];
|
||||
|
||||
// Phase 1:字段级(Trie)
|
||||
$this->walkTrie($data, $this->handlerTrie, $this->removeTrie);
|
||||
|
||||
// Phase 2:节点级(append)
|
||||
$this->applyAppend($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/* ========= Phase 1 ========= */
|
||||
|
||||
protected function walkTrie(
|
||||
&$node,
|
||||
array $handlerNode,
|
||||
array $removeNode
|
||||
): void {
|
||||
if (!is_array($node)) return;
|
||||
|
||||
foreach ($node as $key => &$value) {
|
||||
|
||||
// list:不消费 trie
|
||||
if (is_int($key)) {
|
||||
$this->walkTrie($value, $handlerNode, $removeNode);
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextHandler = $handlerNode[$key] ?? [];
|
||||
$nextRemove = $removeNode[$key] ?? [];
|
||||
|
||||
// 删除优先
|
||||
if (isset($nextRemove['_end'])) {
|
||||
unset($node[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 修改字段
|
||||
if (isset($nextHandler['_fn'])) {
|
||||
$value = ($nextHandler['_fn'])($value);
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$this->walkTrie($value, $nextHandler, $nextRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ========= Phase 2 ========= */
|
||||
|
||||
protected function applyAppend(array &$data): void
|
||||
{
|
||||
foreach ($this->appendRules as $rule) {
|
||||
$this->walkAppend($data, $rule['path'], $rule['fn']);
|
||||
}
|
||||
}
|
||||
|
||||
protected function walkAppend(
|
||||
&$node,
|
||||
array $path,
|
||||
callable $fn
|
||||
): void {
|
||||
if (!is_array($node)) return;
|
||||
|
||||
// 命中节点
|
||||
if (empty($path)) {
|
||||
if (!$this->isList($node)) {
|
||||
$fn($node);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$key = array_shift($path);
|
||||
|
||||
if ($this->isList($node)) {
|
||||
foreach ($node as &$item) {
|
||||
$this->walkAppend($item, array_merge([$key], $path), $fn);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($node[$key])) {
|
||||
$this->walkAppend($node[$key], $path, $fn);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========= Trie ========= */
|
||||
|
||||
protected function insertTrie(array &$trie, array $path, $value): void
|
||||
{
|
||||
$node = &$trie;
|
||||
|
||||
foreach ($path as $segment) {
|
||||
if (!isset($node[$segment])) {
|
||||
$node[$segment] = [];
|
||||
}
|
||||
$node = &$node[$segment];
|
||||
}
|
||||
|
||||
if (is_callable($value)) {
|
||||
$node['_fn'] = $value;
|
||||
} else {
|
||||
$node['_end'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected function isList(array $arr): bool
|
||||
{
|
||||
return array_keys($arr) === range(0, count($arr) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
92
Vagrantfile
vendored
Normal file
92
Vagrantfile
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
require 'yaml'
|
||||
require 'fileutils'
|
||||
|
||||
required_plugins_installed = nil
|
||||
required_plugins = %w( vagrant-hostmanager vagrant-vbguest )
|
||||
required_plugins.each do |plugin|
|
||||
unless Vagrant.has_plugin? plugin
|
||||
system "vagrant plugin install #{plugin}"
|
||||
required_plugins_installed = true
|
||||
end
|
||||
end
|
||||
|
||||
# IF plugin[s] was just installed - restart required
|
||||
if required_plugins_installed
|
||||
# Get CLI command[s] and call again
|
||||
system 'vagrant' + ARGV.to_s.gsub(/\[\"|\", \"|\"\]/, ' ')
|
||||
exit
|
||||
end
|
||||
|
||||
domains = {
|
||||
app: 'yii2basic.test'
|
||||
}
|
||||
|
||||
vagrantfile_dir_path = File.dirname(__FILE__)
|
||||
|
||||
config = {
|
||||
local: vagrantfile_dir_path + '/vagrant/config/vagrant-local.yml',
|
||||
example: vagrantfile_dir_path + '/vagrant/config/vagrant-local.example.yml'
|
||||
}
|
||||
|
||||
# copy config from example if local config not exists
|
||||
FileUtils.cp config[:example], config[:local] unless File.exist?(config[:local])
|
||||
# read config
|
||||
options = YAML.load_file config[:local]
|
||||
|
||||
# check github token
|
||||
if options['github_token'].nil? || options['github_token'].to_s.length != 40
|
||||
puts "You must place REAL GitHub token into configuration:\n/yii2-app-basic/vagrant/config/vagrant-local.yml"
|
||||
exit
|
||||
end
|
||||
|
||||
# vagrant configurate
|
||||
Vagrant.configure(2) do |config|
|
||||
# select the box
|
||||
config.vm.box = 'bento/ubuntu-18.04'
|
||||
|
||||
# should we ask about box updates?
|
||||
config.vm.box_check_update = options['box_check_update']
|
||||
|
||||
config.vm.provider 'virtualbox' do |vb|
|
||||
# machine cpus count
|
||||
vb.cpus = options['cpus']
|
||||
# machine memory size
|
||||
vb.memory = options['memory']
|
||||
# machine name (for VirtualBox UI)
|
||||
vb.name = options['machine_name']
|
||||
end
|
||||
|
||||
# machine name (for vagrant console)
|
||||
config.vm.define options['machine_name']
|
||||
|
||||
# machine name (for guest machine console)
|
||||
config.vm.hostname = options['machine_name']
|
||||
|
||||
# network settings
|
||||
config.vm.network 'private_network', ip: options['ip']
|
||||
|
||||
# sync: folder 'yii2-app-advanced' (host machine) -> folder '/app' (guest machine)
|
||||
config.vm.synced_folder './', '/app', owner: 'vagrant', group: 'vagrant'
|
||||
|
||||
# disable folder '/vagrant' (guest machine)
|
||||
config.vm.synced_folder '.', '/vagrant', disabled: true
|
||||
|
||||
# hosts settings (host machine)
|
||||
config.vm.provision :hostmanager
|
||||
config.hostmanager.enabled = true
|
||||
config.hostmanager.manage_host = true
|
||||
config.hostmanager.ignore_private_ip = false
|
||||
config.hostmanager.include_offline = true
|
||||
config.hostmanager.aliases = domains.values
|
||||
|
||||
# quick fix for failed guest additions installations
|
||||
# config.vbguest.auto_update = false
|
||||
|
||||
# provisioners
|
||||
config.vm.provision 'shell', path: './vagrant/provision/once-as-root.sh', args: [options['timezone'], options['ip']]
|
||||
config.vm.provision 'shell', path: './vagrant/provision/once-as-vagrant.sh', args: [options['github_token']], privileged: false
|
||||
config.vm.provision 'shell', path: './vagrant/provision/always-as-root.sh', run: 'always'
|
||||
|
||||
# post-install message (vagrant console)
|
||||
config.vm.post_up_message = "App URL: http://#{domains[:app]}"
|
||||
end
|
||||
35
assets/AdminAsset.php
Normal file
35
assets/AdminAsset.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace app\assets;
|
||||
|
||||
use yii\web\AssetBundle;
|
||||
|
||||
/**
|
||||
* Main application asset bundle.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class AdminAsset extends AssetBundle
|
||||
{
|
||||
public $basePath = '@webroot';
|
||||
public $baseUrl = '@web';
|
||||
public $css = [
|
||||
// 'css/site.css',
|
||||
'css/admin.css',
|
||||
'css/layui.css',
|
||||
'css/layer.css',
|
||||
'font/iconfont.woff2'
|
||||
];
|
||||
public $js = [
|
||||
];
|
||||
public $depends = [
|
||||
'yii\web\YiiAsset',
|
||||
'yii\bootstrap5\BootstrapAsset'
|
||||
];
|
||||
}
|
||||
36
assets/AppAsset.php
Normal file
36
assets/AppAsset.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace app\assets;
|
||||
|
||||
use yii\web\AssetBundle;
|
||||
|
||||
/**
|
||||
* Main application asset bundle.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class AppAsset extends AssetBundle
|
||||
{
|
||||
public $basePath = '@webroot';
|
||||
public $baseUrl = '@web';
|
||||
public $css = [
|
||||
// 'css/site.css',
|
||||
'css/layui.css',
|
||||
'css/admin.css',
|
||||
'css/layer.css',
|
||||
'font/iconfont.woff2'
|
||||
];
|
||||
public $js = [
|
||||
// 'js/jquery.js',
|
||||
];
|
||||
public $depends = [
|
||||
'yii\web\YiiAsset',
|
||||
'yii\bootstrap5\BootstrapAsset'
|
||||
];
|
||||
}
|
||||
823
brand.txt
Normal file
823
brand.txt
Normal file
@ -0,0 +1,823 @@
|
||||
[{
|
||||
"id": 6977,
|
||||
"name": "\u65b0\u4e16\u5bb6\u65cf"
|
||||
}, {
|
||||
"id": 22569,
|
||||
"name": "\u548c\u8a00"
|
||||
}, {
|
||||
"id": 86493,
|
||||
"name": "\u4e91\u601d\u6728\u60f3"
|
||||
}, {
|
||||
"id": 86539,
|
||||
"name": "\u7ec7\u91cc"
|
||||
}, {
|
||||
"id": 87583,
|
||||
"name": "\u7b80\u598d"
|
||||
}, {
|
||||
"id": 89235,
|
||||
"name": "\u5dfd\u5f69"
|
||||
}, {
|
||||
"id": 94111,
|
||||
"name": "\u6717\u5764"
|
||||
}, {
|
||||
"id": 96581,
|
||||
"name": "\u4e09\u6dfc"
|
||||
}, {
|
||||
"id": 98457,
|
||||
"name": "\u5e84\u5bb9"
|
||||
}, {
|
||||
"id": 98555,
|
||||
"name": "\u77f3\u72ee"
|
||||
}, {
|
||||
"id": 98609,
|
||||
"name": "\u8513\u697c\u862d"
|
||||
}, {
|
||||
"id": 135437,
|
||||
"name": "\u5409\u7965\u658b"
|
||||
}, {
|
||||
"id": 176185,
|
||||
"name": "-by RYOJI OBATA"
|
||||
}, {
|
||||
"id": 179409,
|
||||
"name": "\u4e39\u5c3c\u65af"
|
||||
}, {
|
||||
"id": 179587,
|
||||
"name": "\u5a75\u4e4b\u4e91"
|
||||
}, {
|
||||
"id": 181183,
|
||||
"name": "(A)crypsis"
|
||||
}, {
|
||||
"id": 188961,
|
||||
"name": "\u690d\u6728"
|
||||
}, {
|
||||
"id": 194536,
|
||||
"name": "\u4e0a\u624b\u82b1\u7530"
|
||||
}, {
|
||||
"id": 198855,
|
||||
"name": ".ru"
|
||||
}, {
|
||||
"id": 194598,
|
||||
"name": "\u4e91\u51e4\u601d\u5112"
|
||||
}, {
|
||||
"id": 199655,
|
||||
"name": "#SEN"
|
||||
}, {
|
||||
"id": 207855,
|
||||
"name": "\u00c9tudes"
|
||||
}, {
|
||||
"id": 206285,
|
||||
"name": "(X)S.M.L"
|
||||
}, {
|
||||
"id": 202839,
|
||||
"name": "#TodaBelezaPodeSer"
|
||||
}, {
|
||||
"id": 204531,
|
||||
"name": "\u82b1\u6728\u6df1"
|
||||
}, {
|
||||
"id": 209372,
|
||||
"name": "\u201c\u670d\u201d\u5938\u00b7\u201c\u9970\u201d\u653e"
|
||||
}, {
|
||||
"id": 200059,
|
||||
"name": "\u7396\u5586\u5c0f\u7ae5"
|
||||
}, {
|
||||
"id": 202583,
|
||||
"name": "#CraftIsCool"
|
||||
}, {
|
||||
"id": 209339,
|
||||
"name": "\u4e0a\u624b\uff06HELY\u00b7\u9648\u5b87"
|
||||
}, {
|
||||
"id": 209623,
|
||||
"name": "\u041d\u043e\u0432\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438"
|
||||
}, {
|
||||
"id": 209704,
|
||||
"name": "\u0428\u0418\u0422\u042c PROSTO"
|
||||
}, {
|
||||
"id": 215967,
|
||||
"name": "(di)vision"
|
||||
}, {
|
||||
"id": 219118,
|
||||
"name": "\u51dd\u5948\u6620"
|
||||
}, {
|
||||
"id": 210111,
|
||||
"name": "\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \u0441\u0438\u043b\u0443\u044d\u0442"
|
||||
}, {
|
||||
"id": 211798,
|
||||
"name": "\u041e\u043b\u043e\u0432\u043e"
|
||||
}, {
|
||||
"id": 211878,
|
||||
"name": "\u042d\u0444\u0435\u043c\u0435\u0440\u0438\u0434\u0430 DK"
|
||||
}, {
|
||||
"id": 212677,
|
||||
"name": "\u6734\u4e0e\u7d20"
|
||||
}, {
|
||||
"id": 214536,
|
||||
"name": "\u0421\u0435\u043a\u0446\u0438\u044f"
|
||||
}, {
|
||||
"id": 214792,
|
||||
"name": "\u6fb3\u95e8\u65f6\u5c1a\u6c47\u6f14"
|
||||
}, {
|
||||
"id": 217968,
|
||||
"name": "\u84dd\u9014"
|
||||
}, {
|
||||
"id": 218285,
|
||||
"name": "\u5fb7\u724c&\u745e\u724c"
|
||||
}, {
|
||||
"id": 218902,
|
||||
"name": "\u534e\u88f3\u4e5d\u5dde"
|
||||
}, {
|
||||
"id": 218998,
|
||||
"name": "\u548cROBE"
|
||||
}, {
|
||||
"id": 219929,
|
||||
"name": "\u8d62\u667a\u5c1a"
|
||||
}, {
|
||||
"id": 210393,
|
||||
"name": "+81 BRANCA"
|
||||
}, {
|
||||
"id": 211080,
|
||||
"name": "\u0412\u0418\u041d\u041d\u0418"
|
||||
}, {
|
||||
"id": 211898,
|
||||
"name": "\u0415\u0441\u0435\u043d\u0438\u044f"
|
||||
}, {
|
||||
"id": 211899,
|
||||
"name": "\u041a\u0443\u0437\u043d\u0435\u0446\u043e\u0432\u0430 \u0438 \u0411\u043e\u043d\u0438\u0444\u0430\u0442\u044c\u0435\u0432\u0430"
|
||||
}, {
|
||||
"id": 211900,
|
||||
"name": "\u0421\u0442\u0438\u043b\u044f\u0436\u043a\u0438"
|
||||
}, {
|
||||
"id": 212394,
|
||||
"name": "\u4e00\u4e0a\u5341"
|
||||
}, {
|
||||
"id": 212676,
|
||||
"name": "\u5e74\u8863"
|
||||
}, {
|
||||
"id": 214556,
|
||||
"name": "\u0421\u0427\u0410\u0421\u0422\u042c\u0415"
|
||||
}, {
|
||||
"id": 214591,
|
||||
"name": "+7 HOURS BY IVAN ASEN 22"
|
||||
}, {
|
||||
"id": 214752,
|
||||
"name": "\u9488\u7ec7\u529b\u91cf"
|
||||
}, {
|
||||
"id": 214755,
|
||||
"name": "\u7ae0\u6d77\u71d5"
|
||||
}, {
|
||||
"id": 214757,
|
||||
"name": "\u6750\u8d28\u5b9e\u9a8c"
|
||||
}, {
|
||||
"id": 214758,
|
||||
"name": "\u539f\u6c11\u6587\u5316"
|
||||
}, {
|
||||
"id": 214774,
|
||||
"name": "\u6d6a\u5de2"
|
||||
}, {
|
||||
"id": 214781,
|
||||
"name": "\u5b9e\u9a8c\u7535\u5b50"
|
||||
}, {
|
||||
"id": 214782,
|
||||
"name": "\u5eb6\u6c11\u751f\u6d3b"
|
||||
}, {
|
||||
"id": 214783,
|
||||
"name": "\u6c38\u7eed\u521b\u65b0"
|
||||
}, {
|
||||
"id": 214785,
|
||||
"name": "\u8bbe\u8ba1\u5e08\u8054\u5408\u79c0"
|
||||
}, {
|
||||
"id": 214793,
|
||||
"name": "\u5b66\u751f\u8bbe\u8ba1\u5e08\u4e13\u573a"
|
||||
}, {
|
||||
"id": 214795,
|
||||
"name": "\u539f\u521b\u8bbe\u8ba1\u5927\u8d5b\u603b\u51b3\u8d5b"
|
||||
}, {
|
||||
"id": 214798,
|
||||
"name": "\u65f6\u5c1a\u8bbe\u8ba1\u5b66\u9662\u5b75\u5316\u8bbe\u8ba1\u54c1\u724c\u8054\u6f14"
|
||||
}, {
|
||||
"id": 215031,
|
||||
"name": "\u0421\u043ek\u043en R\u0441hbin"
|
||||
}, {
|
||||
"id": 217179,
|
||||
"name": "\u00c0LEA"
|
||||
}, {
|
||||
"id": 218203,
|
||||
"name": "\u69ff\u7eed\u65d7\u888dx\u4e94\u5343\u9ad8\u8857"
|
||||
}, {
|
||||
"id": 218282,
|
||||
"name": "\u5143\u9882 \u00b7 \u4e1c\u65b9"
|
||||
}, {
|
||||
"id": 218286,
|
||||
"name": "\u84dd\u9edb\u4e39\u5c3c"
|
||||
}, {
|
||||
"id": 218289,
|
||||
"name": "\u6d77\u6d3e"
|
||||
}, {
|
||||
"id": 219397,
|
||||
"name": "\u5341\u4e8c\u9762\u4f53x\u805a\u9053"
|
||||
}, {
|
||||
"id": 219700,
|
||||
"name": "\u0421hereshnivska x Tereza Barabash"
|
||||
}, {
|
||||
"id": 219977,
|
||||
"name": "\u94ed\u8863\u574a"
|
||||
}, {
|
||||
"id": 228189,
|
||||
"name": "\u53e4\u963f\u65b0"
|
||||
}, {
|
||||
"id": 221776,
|
||||
"name": "\u8d70\u5fc3\u00b7\u83a8\u7ef8"
|
||||
}, {
|
||||
"id": 229797,
|
||||
"name": "\u5929\u73ba"
|
||||
}, {
|
||||
"id": 221544,
|
||||
"name": "\u767d\u9e7f\u8bed \u00b7 \u8d75\u6653\u59b9"
|
||||
}, {
|
||||
"id": 221851,
|
||||
"name": "\u7280\u6d41"
|
||||
}, {
|
||||
"id": 224960,
|
||||
"name": "\u9526\u88f3\u7389\u8863"
|
||||
}, {
|
||||
"id": 220131,
|
||||
"name": "\u6652\u8c37\u573a"
|
||||
}, {
|
||||
"id": 224385,
|
||||
"name": "\u91d1\u666f\u6021"
|
||||
}, {
|
||||
"id": 220084,
|
||||
"name": "\u94cc\u91c7\u7eba\u7ec7 X \u610f\u7136\u513f\u670d\u88c5\u8bbe\u8ba1"
|
||||
}, {
|
||||
"id": 221896,
|
||||
"name": "\u68df\u6881 RUNWAY x JUNWEILIN"
|
||||
}, {
|
||||
"id": 221909,
|
||||
"name": "\u6167\u971e"
|
||||
}, {
|
||||
"id": 221914,
|
||||
"name": "\u767d\u9a6cX\u4e2d\u6e2f"
|
||||
}, {
|
||||
"id": 221932,
|
||||
"name": "\u7ca4\u6e2f\u6fb3\u54c1\u724c"
|
||||
}, {
|
||||
"id": 221934,
|
||||
"name": "\u82b1\u8a93"
|
||||
}, {
|
||||
"id": 221935,
|
||||
"name": "\u4e91\u4e1d\u5c1a"
|
||||
}, {
|
||||
"id": 221994,
|
||||
"name": "\u5b8f\u6770\u96c6\u56e2"
|
||||
}, {
|
||||
"id": 224178,
|
||||
"name": "\u79be\u96c0"
|
||||
}, {
|
||||
"id": 225586,
|
||||
"name": "\u5343\u8da3\u4f1a"
|
||||
}, {
|
||||
"id": 225889,
|
||||
"name": "\u5341\u4e8c\u9762\u4f53"
|
||||
}, {
|
||||
"id": 226836,
|
||||
"name": "\u6cca\u96fe"
|
||||
}, {
|
||||
"id": 220081,
|
||||
"name": "\u73ba\u94ed\u8bbe\u8ba1"
|
||||
}, {
|
||||
"id": 220106,
|
||||
"name": "\u534e\u6d77\u8fbe\u8054\u5408\u79c0"
|
||||
}, {
|
||||
"id": 221661,
|
||||
"name": "\u4e2d\u56fd\u674e\u5b81"
|
||||
}, {
|
||||
"id": 221688,
|
||||
"name": "\u559c\u7ed2\u5154"
|
||||
}, {
|
||||
"id": 221735,
|
||||
"name": "\u5706.\u5f5d"
|
||||
}, {
|
||||
"id": 221778,
|
||||
"name": "\u805a\u9053 X \u5996\u5ba2"
|
||||
}, {
|
||||
"id": 221820,
|
||||
"name": "\u601d\u672c\u5802\u00b7\u7f02\u4e1d"
|
||||
}, {
|
||||
"id": 221891,
|
||||
"name": "\u805a\u9053 X LANG COUTURE"
|
||||
}, {
|
||||
"id": 221892,
|
||||
"name": "\u68df\u6881 RUNWAY x DANSHAN"
|
||||
}, {
|
||||
"id": 221893,
|
||||
"name": "\u68df\u6881 RUNWAY x CONCISE"
|
||||
}, {
|
||||
"id": 221907,
|
||||
"name": "\u4e2d\u5c71\u88c5\u6587\u5316\u9986"
|
||||
}, {
|
||||
"id": 221908,
|
||||
"name": "\u7384\u61ac\u9f99"
|
||||
}, {
|
||||
"id": 221910,
|
||||
"name": "\u7814\u7ee3\u5de5\u623f"
|
||||
}, {
|
||||
"id": 221911,
|
||||
"name": "\u4e7e\u946b\u94bb\u9970"
|
||||
}, {
|
||||
"id": 221966,
|
||||
"name": "\u6797\u829d"
|
||||
}, {
|
||||
"id": 221968,
|
||||
"name": "\u5cad\u5357\u8863\u88f3"
|
||||
}, {
|
||||
"id": 221970,
|
||||
"name": "\u4e2d\u56fd\u6797\u829d X \u5218\u4eae"
|
||||
}, {
|
||||
"id": 221998,
|
||||
"name": "\u9999\u683c\u96c5\u4e3d"
|
||||
}, {
|
||||
"id": 222002,
|
||||
"name": "\u81ea\u7136\u800c\u3010\u71c3\u3011"
|
||||
}, {
|
||||
"id": 224012,
|
||||
"name": "\u82b1\u7530\u5f69"
|
||||
}, {
|
||||
"id": 224092,
|
||||
"name": "\u56fd\u8299"
|
||||
}, {
|
||||
"id": 224096,
|
||||
"name": "\u83c1\u754c"
|
||||
}, {
|
||||
"id": 224179,
|
||||
"name": "\u754c\u65d7"
|
||||
}, {
|
||||
"id": 224180,
|
||||
"name": "\u6590\u674b"
|
||||
}, {
|
||||
"id": 224181,
|
||||
"name": "\u4e56\u4e56\u718a"
|
||||
}, {
|
||||
"id": 224182,
|
||||
"name": "\u9f99\u4e91\u4f20"
|
||||
}, {
|
||||
"id": 224184,
|
||||
"name": "\u66e6\u5149"
|
||||
}, {
|
||||
"id": 224185,
|
||||
"name": "\u8fe6\u7136"
|
||||
}, {
|
||||
"id": 224187,
|
||||
"name": "\u5c3c\u53ef\u8da3\u73a9"
|
||||
}, {
|
||||
"id": 224190,
|
||||
"name": "\u5410\u706b\u7f57\u7d22\u739b\u7f07"
|
||||
}, {
|
||||
"id": 224191,
|
||||
"name": "\u9526\u7ee3\u4e2d\u534e"
|
||||
}, {
|
||||
"id": 224383,
|
||||
"name": "\u7eff\u8272\u5e74\u534e"
|
||||
}, {
|
||||
"id": 224957,
|
||||
"name": "\u794e\u8bd7\u742a"
|
||||
}, {
|
||||
"id": 225594,
|
||||
"name": "\u5927\u65b9TAIFAR"
|
||||
}, {
|
||||
"id": 225887,
|
||||
"name": "\u771f\u6797"
|
||||
}, {
|
||||
"id": 227121,
|
||||
"name": "\u5e7c\u60a0"
|
||||
}, {
|
||||
"id": 228185,
|
||||
"name": "\u5220\u96642"
|
||||
}, {
|
||||
"id": 228596,
|
||||
"name": "\u805a\u9053x Always Lang"
|
||||
}, {
|
||||
"id": 228635,
|
||||
"name": "\u4f70\u4f26\u4e16\u5bb6"
|
||||
}, {
|
||||
"id": 228636,
|
||||
"name": "\u6a31\u59ff\u5a1c"
|
||||
}, {
|
||||
"id": 228641,
|
||||
"name": "\u4e16\u6cf0\u667a\u9020"
|
||||
}, {
|
||||
"id": 228642,
|
||||
"name": "\u534e\u7f8e"
|
||||
}, {
|
||||
"id": 228688,
|
||||
"name": "\u7231\u9edb\u00b7\u7231\u7f8e"
|
||||
}, {
|
||||
"id": 236523,
|
||||
"name": "\u5927\u8fde\u65f6\u88c5\u5468"
|
||||
}, {
|
||||
"id": 236969,
|
||||
"name": "\u5e7f\u4e1c\u65f6\u88c5\u5468"
|
||||
}, {
|
||||
"id": 230127,
|
||||
"name": "\u4e0a\u4e45\u6977"
|
||||
}, {
|
||||
"id": 230089,
|
||||
"name": "\u8776\u5f71\u87f2\u87f2"
|
||||
}, {
|
||||
"id": 233005,
|
||||
"name": "\u9f8d\u77f3\u5370\u8ff9"
|
||||
}, {
|
||||
"id": 234566,
|
||||
"name": "\u5e7f\u4e1c\u975e\u9057\u670d\u88c5\u670d\u9970\u4f18\u79c0\u6848\u4f8b\u4f5c\u54c1"
|
||||
}, {
|
||||
"id": 239413,
|
||||
"name": "\u8d70\u5fc3"
|
||||
}, {
|
||||
"id": 230124,
|
||||
"name": "\u4f57\u5bc2"
|
||||
}, {
|
||||
"id": 230174,
|
||||
"name": "\u9f8d\u77f3\u5370\u8ff9&LS"
|
||||
}, {
|
||||
"id": 233007,
|
||||
"name": "\u80a9\u4e0a\u4e91"
|
||||
}, {
|
||||
"id": 232752,
|
||||
"name": "#whysocerealz!"
|
||||
}, {
|
||||
"id": 237415,
|
||||
"name": "\u00c6MONA"
|
||||
}, {
|
||||
"id": 230163,
|
||||
"name": "\u7261\u4e39\u4ead"
|
||||
}, {
|
||||
"id": 230217,
|
||||
"name": "\u68a6\u5e7b\u897f\u6e38\u00d7\u4e09\u5bf8\u76db\u4eac"
|
||||
}, {
|
||||
"id": 232856,
|
||||
"name": "\"\u4e1d\u97f5\u4e1c\u65b9\"\u76db\u6cfd\u65f6\u5c1a"
|
||||
}, {
|
||||
"id": 232923,
|
||||
"name": "\u9996\u5c4a\u5168\u56fd\u5c11\u5e74\u513f\u7ae5\u827a\u672f\u65f6\u5c1a\u98ce\u91c7\u5927\u4f1a"
|
||||
}, {
|
||||
"id": 232939,
|
||||
"name": "\u5173\u96ce"
|
||||
}, {
|
||||
"id": 233400,
|
||||
"name": "\u9192\u72ee\u549a\u549a\u9535"
|
||||
}, {
|
||||
"id": 234247,
|
||||
"name": "\"ZETTAKIT CUP\"1st China Metaverse Fashion Design Competition\/\u6cfd\u5854\u4e91\u676f\u2022\u7b2c\u4e00\u5c4a\u4e2d\u56fd\u5143\u5b87\u5b99\u670d\u88c5\u8bbe\u8ba1\u5927\u8d5b"
|
||||
}, {
|
||||
"id": 234561,
|
||||
"name": "_J.L-A.L_"
|
||||
}, {
|
||||
"id": 236544,
|
||||
"name": "\u5409\u7965\u8863\u5bb6\u00b7\u9093\u6625\u65ed"
|
||||
}, {
|
||||
"id": 239012,
|
||||
"name": "\u0422vid & Ko\/\u0422\u0432\u0438\u0434 & \u041a\u043e"
|
||||
}, {
|
||||
"id": 239319,
|
||||
"name": "\u94ed\u6708\u670d\u9970"
|
||||
}, {
|
||||
"id": 230122,
|
||||
"name": "\u745e\u5c14"
|
||||
}, {
|
||||
"id": 230123,
|
||||
"name": "\u76ae\u57ce\u4e25\u9009"
|
||||
}, {
|
||||
"id": 231012,
|
||||
"name": "\u8d8a\u7ea6\u65f6\u5c1a"
|
||||
}, {
|
||||
"id": 232957,
|
||||
"name": "\u840c\u52a8\u4e00\u5927\u624b\u62c9\u5c0f\u624b\u300a\u98ce\u91c7\u7ae5\u88c5\u300bX bbc X HANAKIMI\u8d8b\u52bf\u53d1\u5e03"
|
||||
}, {
|
||||
"id": 233006,
|
||||
"name": "\u516d\u793c"
|
||||
}, {
|
||||
"id": 234321,
|
||||
"name": "\u8309\u5bfb\/MOUTION"
|
||||
}, {
|
||||
"id": 234553,
|
||||
"name": "\u76c8\u4e91\u79d1\u6280"
|
||||
}, {
|
||||
"id": 234554,
|
||||
"name": "\u6469\u8fea\u83b2\u59ff"
|
||||
}, {
|
||||
"id": 234556,
|
||||
"name": "\u4e91\u6c34\u82b3\u534e"
|
||||
}, {
|
||||
"id": 234557,
|
||||
"name": "\u4f18\u5e03"
|
||||
}, {
|
||||
"id": 234560,
|
||||
"name": "\u7ae0\u9547\u5e03\u827a"
|
||||
}, {
|
||||
"id": 234563,
|
||||
"name": "\u8457\u8863"
|
||||
}, {
|
||||
"id": 234570,
|
||||
"name": "\u5343\u7389\u9999\u7ea6"
|
||||
}, {
|
||||
"id": 234571,
|
||||
"name": "\u65b0\u6750\u6599\u6d41\u884c\u8d8b\u52bf"
|
||||
}, {
|
||||
"id": 237235,
|
||||
"name": "\u4e2d\u534e\u676f\u603b\u51b3\u8d5b"
|
||||
}, {
|
||||
"id": 239161,
|
||||
"name": "\u89c9\u7ec7SENSIYARN"
|
||||
}, {
|
||||
"id": 239297,
|
||||
"name": "\u9b45\u529b\u6696\u57ce\u00b7\u7ed2\u5408\u70ab\u5f69"
|
||||
}, {
|
||||
"id": 239303,
|
||||
"name": "\u53e4\u60a6\u65e5\u5b63"
|
||||
}, {
|
||||
"id": 239305,
|
||||
"name": "\u65f6\u5149\u7ec7\u68a6\u00b7\u7f8a\u7ed2\u534e\u7ae0"
|
||||
}, {
|
||||
"id": 239340,
|
||||
"name": "\u8c37\u66fc\u79cb"
|
||||
}, {
|
||||
"id": 239342,
|
||||
"name": "\u7f8e\u4e3d\u79d8\u5bc6"
|
||||
}, {
|
||||
"id": 239343,
|
||||
"name": "\u540d\u9f20\u7537\u88c5"
|
||||
}, {
|
||||
"id": 239344,
|
||||
"name": "\u96c5\u58eb\u51ef"
|
||||
}, {
|
||||
"id": 239346,
|
||||
"name": "\u6708\u7403"
|
||||
}, {
|
||||
"id": 239357,
|
||||
"name": "\u897f\u7531\u5b9a\u5236"
|
||||
}, {
|
||||
"id": 239359,
|
||||
"name": "\u4e2d\u96cd\u4e91\u7eb1"
|
||||
}, {
|
||||
"id": 239362,
|
||||
"name": "\u5f20\u9896\u83b9 \u00d7 \u4ebf\u7f8e lmate"
|
||||
}, {
|
||||
"id": 239365,
|
||||
"name": "\u5409\u5154\u4ed9"
|
||||
}, {
|
||||
"id": 239390,
|
||||
"name": "\u90fd\u5e02\u65b0\u611f\u89c9"
|
||||
}, {
|
||||
"id": 239391,
|
||||
"name": "\u97e9\u793e"
|
||||
}, {
|
||||
"id": 239392,
|
||||
"name": "\u7c73\u8bd7\u82ac"
|
||||
}, {
|
||||
"id": 239393,
|
||||
"name": "\u7ef4\u591a\u6155"
|
||||
}, {
|
||||
"id": 244183,
|
||||
"name": "\u6d6e\u68a6\u5ba2"
|
||||
}, {
|
||||
"id": 244134,
|
||||
"name": "\u5982\u5b50\u4e4b\u8863"
|
||||
}, {
|
||||
"id": 244180,
|
||||
"name": "\u73ed\u5fb7\u5c3c\u5c14"
|
||||
}, {
|
||||
"id": 244182,
|
||||
"name": "\u5cbd\u65b9\u65e2\u767d"
|
||||
}, {
|
||||
"id": 244185,
|
||||
"name": "\u4e4c\u8499\u6751\u79c0"
|
||||
}, {
|
||||
"id": 244188,
|
||||
"name": "\u74c5\u9526"
|
||||
}, {
|
||||
"id": 241144,
|
||||
"name": "\u76db\u6cfd\u7ec7\u9020"
|
||||
}, {
|
||||
"id": 242878,
|
||||
"name": "\u5e0c\u53ca\u00b7\u4e07\u7269\u767d"
|
||||
}, {
|
||||
"id": 244181,
|
||||
"name": "\u4ee3\u590f"
|
||||
}, {
|
||||
"id": 240329,
|
||||
"name": "\uff08un\uff09decided"
|
||||
}, {
|
||||
"id": 240393,
|
||||
"name": "\u9648\u5217\u5171\u548c"
|
||||
}, {
|
||||
"id": 241150,
|
||||
"name": "\u7d2b\u6676\u82b1 x SHIN"
|
||||
}, {
|
||||
"id": 241170,
|
||||
"name": "\u534e\u5cf0\u5343\u79a7"
|
||||
}, {
|
||||
"id": 241601,
|
||||
"name": "\u805a\u8863\u5802"
|
||||
}, {
|
||||
"id": 241794,
|
||||
"name": "\u70ed\u6c34\u91ce\u4eba"
|
||||
}, {
|
||||
"id": 242876,
|
||||
"name": "\u6b66\u6c49\u767e\u8054\u5965\u7279\u83b1\u65af\u79c0"
|
||||
}, {
|
||||
"id": 243101,
|
||||
"name": "\u65e9\u7a3b\u7530\u5927\u5b66"
|
||||
}, {
|
||||
"id": 243124,
|
||||
"name": "\u7b2c\u4e5d\u5c4a\u4e2d\u56fd\u56fd\u9645\u65f6\u88c5\u8bbe\u8ba1\u521b\u65b0\u4f5c\u54c1\u5927\u8d5b"
|
||||
}, {
|
||||
"id": 244056,
|
||||
"name": "\u66fc\u9640\u5fc3"
|
||||
}, {
|
||||
"id": 244130,
|
||||
"name": "\u5e72\u58eb"
|
||||
}, {
|
||||
"id": 244174,
|
||||
"name": "\u76d0\u8fb9"
|
||||
}, {
|
||||
"id": 244175,
|
||||
"name": "\u8d3a\u82e1\u58a8"
|
||||
}, {
|
||||
"id": 244184,
|
||||
"name": "\u5357\u5c71\u667a\u5c1a\u676f"
|
||||
}, {
|
||||
"id": 244192,
|
||||
"name": "\u7d2b\u6676\u82b1 \u2573 PISKULINA"
|
||||
}, {
|
||||
"id": 244216,
|
||||
"name": "\u5149\u5408\u9ad8\u5b9a"
|
||||
}, {
|
||||
"id": 244221,
|
||||
"name": "\u8543\u5df4\u79c0"
|
||||
}, {
|
||||
"id": 244603,
|
||||
"name": "\u4e16\u827a\u4f1a"
|
||||
}, {
|
||||
"id": 244607,
|
||||
"name": "\u65f6\u5c1a\u6c99\u6eaa\u98ce"
|
||||
}, {
|
||||
"id": 244613,
|
||||
"name": "\u65ed\u65e5\u5ee3\u6771\u670d\u88c5\u5b66\u9662"
|
||||
}, {
|
||||
"id": 244654,
|
||||
"name": "\u4e2d\u56fd\u80e1\u8f69"
|
||||
}, {
|
||||
"id": 244886,
|
||||
"name": "\u6d59\u6c5f\u8d22\u7ecf\u5927\u5b66\u4e1c\u65b9\u5b66\u9662"
|
||||
}, {
|
||||
"id": 244928,
|
||||
"name": "\u6b66\u6c49\u4f53\u80b2\u5b66\u9662"
|
||||
}, {
|
||||
"id": 244929,
|
||||
"name": "\u5185\u8499\u53e4\u827a\u672f\u5b66\u9662"
|
||||
}, {
|
||||
"id": 245005,
|
||||
"name": "\u7b2c 30 \u5c4a\u4e2d\u56fd\u65f6\u88c5\u8bbe\u8ba1\u65b0\u4eba\u5956\u8bc4\u9009"
|
||||
}, {
|
||||
"id": 245050,
|
||||
"name": "\u676d\u5dde\u8f7b\u5de5\u6280\u5e08\u5b66\u9662"
|
||||
}, {
|
||||
"id": 245257,
|
||||
"name": "\u5f20\u69ce\u9488\u7ec7\u96c6\u7fa4\u54c1\u724c\u8054\u5408\u79c0"
|
||||
}, {
|
||||
"id": 245735,
|
||||
"name": "\u6da6\u6052"
|
||||
}, {
|
||||
"id": 245747,
|
||||
"name": "\u7b2c\u4e8c\u5341\u516b\u5c4a\u201c\u771f\u76ae\u6807\u5fd7\u676f\u201d"
|
||||
}, {
|
||||
"id": 245782,
|
||||
"name": "\u7ca4\u6e2f\u6fb3\u5927\u6e7e\u533a\u65f6\u5c1a\u6c47\u6f14"
|
||||
}, {
|
||||
"id": 246281,
|
||||
"name": "\u804a\u57ce\u68c9\u670d"
|
||||
}, {
|
||||
"id": 247209,
|
||||
"name": "\u9752\u5e74\u6bdb\u7ec7\u8bbe\u8ba1\u5e08\u5927\u8d5b"
|
||||
}, {
|
||||
"id": 240112,
|
||||
"name": "\u4e1c\u4eac\u7b2c98\u5c4a\u65f6\u88c5\u5927\u8d5b\u88c5\u82d1\u8d4f"
|
||||
}, {
|
||||
"id": 241122,
|
||||
"name": "\u683c\u6851"
|
||||
}, {
|
||||
"id": 241123,
|
||||
"name": "\u661f\u9645\u201c\u6f2b\u201d\u6b65"
|
||||
}, {
|
||||
"id": 241124,
|
||||
"name": "\u4e0e\u5149\u540c\u822a"
|
||||
}, {
|
||||
"id": 241125,
|
||||
"name": "\u66ae\u5149\u7ec7\u68a6"
|
||||
}, {
|
||||
"id": 241126,
|
||||
"name": "\u6c14\u5019\u9038\u52a8"
|
||||
}, {
|
||||
"id": 241579,
|
||||
"name": "\u886c\u886b\u8001\u7f57"
|
||||
}, {
|
||||
"id": 241623,
|
||||
"name": "\u4e91\u559c\u7eb1\u534e"
|
||||
}, {
|
||||
"id": 241795,
|
||||
"name": "\u516c\u4e3b\u5149\u73af"
|
||||
}, {
|
||||
"id": 241931,
|
||||
"name": "\u4e00\u7740\u00b7\u7ae0\u6d77\u71d5"
|
||||
}, {
|
||||
"id": 241936,
|
||||
"name": "\u53e4\u62d9\u8d28\u9020\/\u6e05\u96c5\u6cfd\u00b7\u53e4\u62d9\u8d28\u9020"
|
||||
}, {
|
||||
"id": 242235,
|
||||
"name": "_J.L-A.L_ x Marzotto"
|
||||
}, {
|
||||
"id": 242811,
|
||||
"name": "\u7f20\u4e4b\u82b1\u5de5\u574a&\u7eba\u5927\u67d3\u8bed"
|
||||
}, {
|
||||
"id": 242874,
|
||||
"name": "\u65b9\u4f9d\u4f9d"
|
||||
}, {
|
||||
"id": 242875,
|
||||
"name": "\u672a\u89e3"
|
||||
}, {
|
||||
"id": 242879,
|
||||
"name": "\u715c\u79c0\u5802"
|
||||
}, {
|
||||
"id": 243136,
|
||||
"name": "\u6df1\u5733\u529b\u91cf\u65f6\u88c5\u8bbe\u8ba1\u5e08\u4f5c\u54c1\u8054\u5408\u53d1\u5e03"
|
||||
}, {
|
||||
"id": 243137,
|
||||
"name": "\u7ca4\u6e2f\u6fb3\u5927\u6e7e\u533a\u540d\u5e08\u4f5c\u54c1\u8054\u5408\u53d1\u5e03"
|
||||
}, {
|
||||
"id": 244186,
|
||||
"name": "\u5a74\u4e8c\u4ee3"
|
||||
}, {
|
||||
"id": 244187,
|
||||
"name": "\u4e2d\u6e2f\u76ae\u5177\u57ce"
|
||||
}, {
|
||||
"id": 244189,
|
||||
"name": "\u6e7e\u533a\u9999\u4e91\u7eb1\u9762\u6599\u7814\u53d1\u4e2d\u5fc3"
|
||||
}, {
|
||||
"id": 244191,
|
||||
"name": "\u5fae\u5149\u5c9b"
|
||||
}, {
|
||||
"id": 244218,
|
||||
"name": "\u827e\u831c\u5a1c\u53e4\u7ee3\u5e84"
|
||||
}, {
|
||||
"id": 244219,
|
||||
"name": "\u4fdd\u5170\u5fb7&\u53cb\u8c0a\u4f7f\u8005"
|
||||
}, {
|
||||
"id": 244220,
|
||||
"name": "\u521d\u526a"
|
||||
}, {
|
||||
"id": 244222,
|
||||
"name": "\u5357\u65b9\u7425\u73c0"
|
||||
}, {
|
||||
"id": 244223,
|
||||
"name": "\u6e05\u541b"
|
||||
}, {
|
||||
"id": 244471,
|
||||
"name": "\u4e94\u6307\u5c71Miss\u9ece\u5927\u79c0"
|
||||
}, {
|
||||
"id": 244507,
|
||||
"name": "\u4e2d\u56fd\u65d7\u888d\u00b7\u9ece\u97f5"
|
||||
}, {
|
||||
"id": 244574,
|
||||
"name": "\u6f6e\u73a9\u65f6\u88c5\u8de8\u754c\u5148\u950b\u8bbe\u8ba1\u5e08"
|
||||
}, {
|
||||
"id": 244615,
|
||||
"name": "\u5c15\u5a03\u5566"
|
||||
}, {
|
||||
"id": 244633,
|
||||
"name": "\u5e7f\u6e05\u7eba\u7ec7\u56ed"
|
||||
}, {
|
||||
"id": 245051,
|
||||
"name": "\u676d\u5dde\u7f8e\u672f\u804c\u4e1a\u5b66\u6821"
|
||||
}, {
|
||||
"id": 245734,
|
||||
"name": "\u76ae\u57ce\u4e25\u9009\u54c1\u724c\u8054\u5408"
|
||||
}, {
|
||||
"id": 245736,
|
||||
"name": "\u5723\u739b\u4e01\u56fd\u9645\u65f6\u88c5\u5468\u73ed"
|
||||
}, {
|
||||
"id": 246208,
|
||||
"name": "\u5f20\u6d2a"
|
||||
}, {
|
||||
"id": 246247,
|
||||
"name": "\u97e9\u5b9c"
|
||||
}, {
|
||||
"id": 246956,
|
||||
"name": "\u8bd1\u4f9d"
|
||||
}, {
|
||||
"id": 247210,
|
||||
"name": "\u9999\u96f2\u6613\u898b"
|
||||
}, {
|
||||
"id": 247230,
|
||||
"name": "\u6c49\u9526\u96c6"
|
||||
}, {
|
||||
"id": 247277,
|
||||
"name": "\u91d1\u5b9d\u4e2d\u5f0f"
|
||||
}]
|
||||
27
codeception.yml
Normal file
27
codeception.yml
Normal file
@ -0,0 +1,27 @@
|
||||
actor: Tester
|
||||
bootstrap: _bootstrap.php
|
||||
paths:
|
||||
tests: tests
|
||||
output: tests/_output
|
||||
data: tests/_data
|
||||
helpers: tests/_support
|
||||
settings:
|
||||
memory_limit: 1024M
|
||||
colors: true
|
||||
modules:
|
||||
config:
|
||||
Yii2:
|
||||
configFile: 'config/test.php'
|
||||
|
||||
# To enable code coverage:
|
||||
#coverage:
|
||||
# #c3_url: http://localhost:8080/index-test.php/
|
||||
# enabled: true
|
||||
# #remote: true
|
||||
# #remote_config: '../codeception.yml'
|
||||
# whitelist:
|
||||
# include:
|
||||
# - models/*
|
||||
# - controllers/*
|
||||
# - commands/*
|
||||
# - mail/*
|
||||
137
commands/HelloController.php
Normal file
137
commands/HelloController.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace app\commands;
|
||||
|
||||
use app\common\FileHelper;
|
||||
use app\common\SaltHelper;
|
||||
use app\enums\SourceEnum;
|
||||
use app\models\Brand;
|
||||
use app\models\BrandAlias;
|
||||
use app\models\BrandRunwayImages;
|
||||
use app\models\BrandSource;
|
||||
use app\models\logics\commands\SpiderVogue;
|
||||
use app\models\User;
|
||||
use Qiniu\Auth;
|
||||
use Qiniu\Storage\UploadManager;
|
||||
use yii\console\Controller;
|
||||
use yii\console\ExitCode;
|
||||
|
||||
/**
|
||||
* This command echoes the first argument that you have entered.
|
||||
*
|
||||
* This command is provided as an example for you to learn how to create console commands.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class HelloController extends Controller
|
||||
{
|
||||
/**
|
||||
* This command echoes what you have entered as the message.
|
||||
* @param string $message the message to be echoed.
|
||||
* @return int Exit code
|
||||
*/
|
||||
public function actionIndex($message = 'hello world')
|
||||
{
|
||||
|
||||
foreach(BrandRunwayImages::find()->each() as $item) {
|
||||
var_dump($item['image']);
|
||||
$cont = file_get_contents($item['image']);
|
||||
$imageName = md5(SaltHelper::addSalt($item['image']));
|
||||
$imagePath = md5(SaltHelper::addSalt($item['runway_id']));
|
||||
|
||||
if (!is_dir(\Yii::getAlias('@runtime') . "/{$imagePath}")) {
|
||||
FileHelper::createDirectory(\Yii::getAlias('@runtime') . "/{$imagePath}");
|
||||
}
|
||||
|
||||
file_put_contents(\Yii::getAlias('@runtime') . "/{$imagePath}/{$imageName}", $cont);
|
||||
$accessKey = '7GLHrN7BqrI9JnWZ9Pki2q5rqPazhIFroo19a-Av';
|
||||
$secretKey = 'gmg6PLgg666Cme-gsyTlsBLhshDv-6_zsEmW4jRY';
|
||||
$auth = new Auth($accessKey, $secretKey);
|
||||
$bucket = '23cm';
|
||||
// 生成上传Token
|
||||
$token = $auth->uploadToken($bucket);
|
||||
// 要上传文件的本地路径
|
||||
$filePath = \Yii::getAlias('@runtime') . "/{$imagePath}/{$imageName}";
|
||||
// 上传到存储后保存的文件名
|
||||
$key = "{$imagePath}/{$imageName}";
|
||||
// 初始化 UploadManager 对象并进行文件的上传。
|
||||
$uploadMgr = new UploadManager();
|
||||
// 调用 UploadManager 的 putFile 方法进行文件的上传。
|
||||
list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath, null, 'application/octet-stream', true, null, 'v2');
|
||||
echo "\n====> putFile result: \n";
|
||||
if ($err !== null) {
|
||||
var_dump($err);
|
||||
} else {
|
||||
var_dump($ret);
|
||||
}
|
||||
// 构建 UploadManager 对象
|
||||
// $uploadMgr = new UploadManager();
|
||||
$qu = BrandRunwayImages::findOne($item['id']);
|
||||
$qu->name = $key;
|
||||
$qu->save();
|
||||
// die;
|
||||
}
|
||||
die;
|
||||
foreach(BrandSource::find()->each() as $item) {
|
||||
$brandName = Brand::find()->where(['id' => $item['brand_id']])->one()->name;
|
||||
$brandName = strtr($brandName, [
|
||||
' ' => '-',
|
||||
'.' => '-'
|
||||
]);
|
||||
|
||||
var_dump(strtolower($brandName));
|
||||
$q = BrandSource::findOne($item['id']);
|
||||
$q->source_url = '/fashion-shows/designer/'.strtolower($brandName);
|
||||
$q->save();
|
||||
}
|
||||
die;
|
||||
$model = new SpiderVogue();
|
||||
$model->setBrandName('gucci');
|
||||
var_dump($model->start());
|
||||
die;
|
||||
ini_set('memory_limit', '1024M');
|
||||
|
||||
$content = file_get_contents(\Yii::$app->basePath . '/brand.txt');
|
||||
var_dump($content);
|
||||
$content = (json_decode($content, true));
|
||||
// var_dump(json_last_error());
|
||||
// var_dump(json_last_error_msg());
|
||||
|
||||
// var_dump($content);die;
|
||||
foreach ($content as $item) {
|
||||
$model = new Brand();
|
||||
if (stripos($item['name'], '/') !== false) {
|
||||
$alias = explode('/', $item['name']);
|
||||
foreach ($alias as $i => $aliasItem) {
|
||||
if ($i == 0) {
|
||||
$model->name = $aliasItem;
|
||||
$model->show_name = $item['name'];
|
||||
$model->save();
|
||||
$lastId = $model->id;
|
||||
var_dump($model->errors);
|
||||
} else {
|
||||
$aliasModel = new BrandAlias();
|
||||
$aliasModel->brand_id = $lastId;
|
||||
$aliasModel->name = $aliasItem;
|
||||
$aliasModel->save();
|
||||
var_dump($aliasModel->errors);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$model->name = $item['name'];
|
||||
$model->show_name = $item['name'];
|
||||
$model->save();
|
||||
// var_dump($model->errors);
|
||||
}
|
||||
}
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
}
|
||||
68
commands/SpiderController.php
Normal file
68
commands/SpiderController.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace app\commands;
|
||||
|
||||
use app\common\CommonHelper;
|
||||
use app\common\CurlApp;
|
||||
use app\common\MobileHelper;
|
||||
use app\models\Brand;
|
||||
use app\models\BrandRunway;
|
||||
use app\models\BrandSource;
|
||||
use app\models\Clue;
|
||||
use app\models\jobs\RunwayJob;
|
||||
use app\models\logics\commands\SpiderVogue;
|
||||
use app\models\Oauth;
|
||||
use app\models\OauthAccount;
|
||||
use app\models\OauthAccountLocal;
|
||||
use app\models\User;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use yii\console\Controller;
|
||||
use yii\console\ExitCode;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
|
||||
/**
|
||||
* This command echoes the first argument that you have entered.
|
||||
*
|
||||
* This command is provided as an example for you to learn how to create console commands.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class SpiderController extends Controller
|
||||
{
|
||||
|
||||
|
||||
public function __construct($id, $module, $config = [])
|
||||
{
|
||||
ini_set('pcre.backtrack_limit', '-1');
|
||||
parent::__construct($id, $module, $config);
|
||||
}
|
||||
|
||||
|
||||
public function actionVogue()
|
||||
{
|
||||
$model = new SpiderVogue();
|
||||
$model->start();
|
||||
}
|
||||
|
||||
public function actionRunway()
|
||||
{
|
||||
|
||||
foreach (BrandSource::find()->where(['is_deleted' => 0])->each() as $index => $item) {
|
||||
if ($index == 1) {
|
||||
\Yii::$app->queue->push(new RunwayJob([
|
||||
'source' => $item,
|
||||
]));die;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
13
common/CommonHelper.php
Normal file
13
common/CommonHelper.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace app\common;
|
||||
|
||||
class CommonHelper
|
||||
{
|
||||
public static function getYear(string $title)
|
||||
{
|
||||
preg_match('/([0-9]+)/', $title, $y);
|
||||
|
||||
return $y[1] ?? date('Y');
|
||||
}
|
||||
}
|
||||
167
common/CurlApp.php
Normal file
167
common/CurlApp.php
Normal file
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace app\common;
|
||||
|
||||
class CurlApp
|
||||
{
|
||||
private $ch = null;
|
||||
|
||||
private array $options = [];
|
||||
|
||||
private string $headerContentType = '';
|
||||
|
||||
private string $error = '';
|
||||
|
||||
const METHOD_POST = 'POST';
|
||||
|
||||
const METHOD_GET = 'GET';
|
||||
|
||||
const CONTENT_TYPE_JSON = 'Content-Type: application/json';
|
||||
|
||||
const CONTENT_TYPE_FORM_URLENCODED = 'Content-Type: application/x-www-form-urlencoded';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->ch = curl_init();
|
||||
$this->options[CURLOPT_CONNECTTIMEOUT] = 30;
|
||||
$this->options[CURLOPT_TIMEOUT] = 30;
|
||||
}
|
||||
|
||||
public function setUrl(string $url)
|
||||
{
|
||||
$this->options[CURLOPT_URL] = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCookie(string $cookie)
|
||||
{
|
||||
$this->options[CURLOPT_COOKIE] = $cookie;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setReturnTransfer(bool $isReturn = true)
|
||||
{
|
||||
$this->options[CURLOPT_RETURNTRANSFER] = $isReturn;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPostData($data)
|
||||
{
|
||||
$this->options[CURLOPT_POSTFIELDS] = $data;
|
||||
if ($this->headerContentType === self::CONTENT_TYPE_FORM_URLENCODED) {
|
||||
if (is_array($data)) {
|
||||
$this->options[CURLOPT_POSTFIELDS] = http_build_query($data);
|
||||
}
|
||||
} elseif ($this->headerContentType === self::CONTENT_TYPE_JSON) {
|
||||
if (is_array($data)) {
|
||||
$this->options[CURLOPT_POSTFIELDS] = json_encode($data);
|
||||
} else {
|
||||
$this->options[CURLOPT_POSTFIELDS] = ($data);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setHttpHeader(array $header = [])
|
||||
{
|
||||
foreach ($header as $key => $value) {
|
||||
$this->options[CURLOPT_HTTPHEADER][$key] = $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setProxy($ip, $port)
|
||||
{
|
||||
// 基本代理
|
||||
$this->options[CURLOPT_PROXY] = "{$ip}:{$port}";
|
||||
// 指定代理类型(可选:HTTP、SOCKS4、SOCKS5)
|
||||
$this->options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
|
||||
}
|
||||
|
||||
public function setContentType($contentType)
|
||||
{
|
||||
$this->headerContentType = $contentType;
|
||||
$this->options[CURLOPT_HTTPHEADER][] = $contentType;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMethod($method = 'GET')
|
||||
{
|
||||
$this->options[CURLOPT_POST] = !(strtolower($method) == 'get');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function execMulti($urls = []): array
|
||||
{
|
||||
$multiHandle = curl_multi_init();
|
||||
$handles = [];
|
||||
$responses = [];
|
||||
|
||||
foreach ($urls as $key => $url) {
|
||||
$ch = curl_init();
|
||||
$this->options[CURLOPT_CONNECTTIMEOUT] = 60;
|
||||
$this->options[CURLOPT_TIMEOUT] = 60;
|
||||
// 继承当前 CurlApp 的 options
|
||||
$options = $this->options;
|
||||
|
||||
$options[CURLOPT_URL] = $url;
|
||||
$options[CURLOPT_RETURNTRANSFER] = true;
|
||||
|
||||
curl_setopt_array($ch, $options);
|
||||
|
||||
curl_multi_add_handle($multiHandle, $ch);
|
||||
$handles[$key] = $ch;
|
||||
}
|
||||
|
||||
// 执行
|
||||
$running = null;
|
||||
do {
|
||||
$status = curl_multi_exec($multiHandle, $running);
|
||||
if ($running) {
|
||||
curl_multi_select($multiHandle, 1);
|
||||
}
|
||||
} while ($running && $status === CURLM_OK);
|
||||
|
||||
// 收集结果
|
||||
foreach ($handles as $key => $ch) {
|
||||
$responses[$key] = curl_multi_getcontent($ch);
|
||||
curl_multi_remove_handle($multiHandle, $ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
|
||||
curl_multi_close($multiHandle);
|
||||
|
||||
return $responses;
|
||||
|
||||
}
|
||||
|
||||
public function exec()
|
||||
{
|
||||
$defaultOptions = [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
];
|
||||
|
||||
foreach ($defaultOptions as $key => $val) {
|
||||
if (!isset($this->options[$key])) {
|
||||
$this->options[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
curl_setopt_array($this->ch, $this->options);
|
||||
$resp = curl_exec($this->ch);
|
||||
|
||||
if (curl_errno($this->ch)) {
|
||||
$this->error = curl_error($this->ch);
|
||||
return false;
|
||||
}
|
||||
|
||||
curl_close($this->ch);
|
||||
return $resp;
|
||||
}
|
||||
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
}
|
||||
283
common/FileHelper.php
Normal file
283
common/FileHelper.php
Normal file
@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
namespace app\common;
|
||||
|
||||
use yii\helpers\BaseFileHelper;
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* Class FileHelper
|
||||
* @package common\helpers
|
||||
* @author jianyan74 <751393839@qq.com>
|
||||
*/
|
||||
class FileHelper extends BaseFileHelper
|
||||
{
|
||||
public $_speed = 0;
|
||||
/**
|
||||
* 检测目录并循环创建目录
|
||||
*
|
||||
* @param $catalogue
|
||||
*/
|
||||
public static function mkdirs($catalogue)
|
||||
{
|
||||
if (!file_exists($catalogue)) {
|
||||
self::mkdirs(dirname($catalogue));
|
||||
mkdir($catalogue, 0777);
|
||||
exec('chown -R www.www '.$catalogue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入日志
|
||||
*
|
||||
* @param $path
|
||||
* @param $content
|
||||
* @return bool|int
|
||||
*/
|
||||
public static function writeLog($path, $content)
|
||||
{
|
||||
$use_num = self::sysProbe();
|
||||
// $use_num = Yii::$app->redis->get(CacheKeyEnum::SYSTEM_PROBE);
|
||||
if ($use_num > 98) {
|
||||
return false;
|
||||
}
|
||||
self::mkdirs(dirname($path));
|
||||
return file_put_contents($path, date('Y-m-d H:i:s') . ">>>" . $content . "\r\n", FILE_APPEND);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测磁盘空间
|
||||
* @return bool|int|string
|
||||
*/
|
||||
public static function sysProbe()
|
||||
{
|
||||
$total = round(@disk_total_space(".") / (1024 * 1024 * 1024), 2);
|
||||
$free = round(@disk_free_space(".") / (1024 * 1024 * 1024), 2);
|
||||
$use_num = intval($total - $free) ? : 1;
|
||||
|
||||
// exec("df -h", $systemInfo);
|
||||
// $disk = $systemInfo[5] ?? $systemInfo[1]; //没有找到其他磁盘就默认第一个
|
||||
// $use_num = 1;
|
||||
// if ($disk) {
|
||||
// $use_num = trim(substr($disk, -10, -7)) ? : 1; //媒介保后端服务器磁盘用量
|
||||
// }
|
||||
// Yii::$app->redis->setex(CacheKeyEnum::SYSTEM_PROBE, 3600, $use_num);
|
||||
return $use_num;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件夹大小
|
||||
*
|
||||
* @param string $dir 根文件夹路径
|
||||
* @return int
|
||||
*/
|
||||
public static function getDirSize($dir)
|
||||
{
|
||||
$handle = opendir($dir);
|
||||
$sizeResult = 0;
|
||||
while (false !== ($FolderOrFile = readdir($handle))) {
|
||||
if ($FolderOrFile != "." && $FolderOrFile != "..") {
|
||||
if (is_dir("$dir/$FolderOrFile")) {
|
||||
$sizeResult += self::getDirSize("$dir/$FolderOrFile");
|
||||
}
|
||||
else {
|
||||
$sizeResult += filesize("$dir/$FolderOrFile");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir($handle);
|
||||
return $sizeResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于数组创建目录
|
||||
*
|
||||
* @param $files
|
||||
*/
|
||||
public static function createDirOrFiles($files)
|
||||
{
|
||||
foreach ($files as $key => $value) {
|
||||
if (substr($value, -1) == '/') {
|
||||
mkdir($value);
|
||||
}
|
||||
else {
|
||||
file_put_contents($value, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件大小字节转换对应的单位
|
||||
* @param $size
|
||||
* @return string
|
||||
*/
|
||||
public static function convert($size)
|
||||
{
|
||||
$unit = array('b','kb','MB','GB','tb','pb');
|
||||
return round($size / pow(1024, ($i = floor(log($size, 1024)))), 2) . '' . $unit[$i];
|
||||
}
|
||||
|
||||
public static function downloadVideo($url)
|
||||
{
|
||||
// $is_url = Url::isUrl($url);
|
||||
// if (!$is_url) {
|
||||
// exit('不是正确的链接地址'); //exit掉,下载下来打开会显示无法播放,格式不支持,文件已损坏等
|
||||
// }
|
||||
//获取文件信息
|
||||
// $fileExt = pathinfo($url);
|
||||
//获取文件的扩展名
|
||||
// $allowDownExt = array ('mp4', 'mov');
|
||||
//检测文件类型是否允许下载
|
||||
// if (!in_array($fileExt['extension'], $allowDownExt)) {
|
||||
// exit('不支持该格式');
|
||||
// }
|
||||
// 设置浏览器下载的文件名,这里还以原文件名一样
|
||||
$filename = basename($url);
|
||||
// 获取远程文件大小
|
||||
// 注意filesize()无法获取远程文件大小
|
||||
$headers = get_headers($url, 1);
|
||||
$fileSize = $headers['Content-Length'];
|
||||
if (ini_get('zlib.output_compression')) {
|
||||
ini_set('zlib.output_compression', 'Off');
|
||||
}
|
||||
header_remove('Content-Encoding');
|
||||
// 设置header头
|
||||
// 因为不知道文件是什么类型的,告诉浏览器输出的是字节流
|
||||
header('Content-Type: application/octet-stream');
|
||||
// 告诉浏览器返回的文件大小类型是字节
|
||||
header('Accept-Ranges:bytes');
|
||||
// 告诉浏览器返回的文件大小
|
||||
header('Content-Length: ' . $fileSize);
|
||||
// 告诉浏览器文件作为附件处理并且设定最终下载完成的文件名称
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
//针对大文件,规定每次读取文件的字节数为4096字节,直接输出数据
|
||||
|
||||
$read_buffer = 4096; //4096
|
||||
$handle = fopen($url, 'rb');
|
||||
//总的缓冲的字节数
|
||||
$sum_buffer = 0;
|
||||
//只要没到文件尾,就一直读取
|
||||
while (!feof($handle) && $sum_buffer < $fileSize) {
|
||||
echo fread($handle, $read_buffer);
|
||||
$sum_buffer += $read_buffer;
|
||||
}
|
||||
fclose($handle);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $file 要下载的文件路径
|
||||
* @param String $name 文件名称,为空则与下载的文件名称一样
|
||||
* @param boolean $reload 是否开启断点续传
|
||||
* @return string
|
||||
*/
|
||||
public static function downloadFile($file, $name = '', $reload = false)
|
||||
{
|
||||
$log_path = Yii::getAlias('@runtime') . '/api/' . date('Ym') . '/' . date('d') . '/download.txt';
|
||||
FileHelper::writeLog($log_path, $file);
|
||||
$fp = fopen($file, 'rb');
|
||||
if ($fp) {
|
||||
if ($name == '') {
|
||||
$name = basename($file);
|
||||
}
|
||||
$header_array = get_headers($file, true);
|
||||
// 下载本地文件,获取文件大小
|
||||
if (!$header_array) {
|
||||
$file_size = filesize($file);
|
||||
} else {
|
||||
$file_size = $header_array['Content-Length'];
|
||||
}
|
||||
FileHelper::writeLog($log_path, json_encode($_SERVER, JSON_UNESCAPED_UNICODE));
|
||||
if (isset($_SERVER['HTTP_RANGE']) && !empty($_SERVER['HTTP_RANGE'])) {
|
||||
$ranges = self::getRange($file_size);
|
||||
} else {
|
||||
//第一次连接
|
||||
$size2 = $file_size - 1;
|
||||
header("Content-Range: bytes 0-$size2/$file_size"); //Content-Range: bytes 0-4988927/4988928
|
||||
header("Content-Length: " . $file_size); //输出总长
|
||||
}
|
||||
|
||||
$ua = $_SERVER["HTTP_USER_AGENT"];//判断是什么类型浏览器
|
||||
header('cache-control:public');
|
||||
header('content-type:application/octet-stream');
|
||||
|
||||
$encoded_filename = urlencode($name);
|
||||
$encoded_filename = str_replace("+", "%20", $encoded_filename);
|
||||
|
||||
//解决下载文件名乱码
|
||||
if (preg_match("/MSIE/", $ua) || preg_match("/Trident/", $ua)) {
|
||||
header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
|
||||
} else if (preg_match("/Firefox/", $ua)) {
|
||||
header('Content-Disposition: attachment; filename*="utf8\'\'' . $name . '"');
|
||||
} else if (preg_match("/Chrome/", $ua)) {
|
||||
header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
|
||||
} else {
|
||||
header('Content-Disposition: attachment; filename="' . $name . '"');
|
||||
}
|
||||
//header('Content-Disposition: attachment; filename="' . $name . '"');
|
||||
|
||||
if ($reload && $ranges != null) { // 使用续传
|
||||
header('HTTP/1.1 206 Partial Content');
|
||||
header('Accept-Ranges:bytes');
|
||||
|
||||
// 剩余长度
|
||||
header(sprintf('content-length:%u', $ranges['end'] - $ranges['start']));
|
||||
|
||||
// range信息
|
||||
header(sprintf('content-range:bytes %s-%s/%s', $ranges['start'], $ranges['end'], $file_size));
|
||||
FileHelper::writeLog($log_path, sprintf('content-length:%u', $ranges['end'] - $ranges['start']));
|
||||
// fp指针跳到断点位置
|
||||
fseek($fp, sprintf('%u', $ranges['start']));
|
||||
} else {
|
||||
header('HTTP/1.1 200 OK');
|
||||
header('content-length:' . $file_size);
|
||||
}
|
||||
|
||||
while (!feof($fp)) {
|
||||
echo fread($fp, 4096);
|
||||
ob_flush();
|
||||
}
|
||||
($fp != null) && fclose($fp);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/** 设置下载速度
|
||||
* @param int $speed
|
||||
*/
|
||||
public function setSpeed($speed)
|
||||
{
|
||||
if (is_numeric($speed) && $speed > 16 && $speed < 4096) {
|
||||
$this->_speed = $speed;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取header range信息
|
||||
* @param int $file_size 文件大小
|
||||
* @return Array
|
||||
*/
|
||||
private static function getRange($file_size)
|
||||
{
|
||||
if (isset($_SERVER['HTTP_RANGE']) && !empty($_SERVER['HTTP_RANGE'])) {
|
||||
$range = $_SERVER['HTTP_RANGE'];
|
||||
$range = preg_replace('/[\s|,].*/', '', $range);
|
||||
$range = explode('-', substr($range, 6));
|
||||
if (count($range) < 2) {
|
||||
$range[1] = $file_size;
|
||||
}
|
||||
$range = array_combine(array('start','end'), $range);
|
||||
if (empty($range['start'])) {
|
||||
$range['start'] = 0;
|
||||
}
|
||||
if (empty($range['end'])) {
|
||||
$range['end'] = $file_size;
|
||||
}
|
||||
return $range;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
16
common/ImageHelper.php
Normal file
16
common/ImageHelper.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\common;
|
||||
|
||||
class ImageHelper
|
||||
{
|
||||
public static function imageMogr2H480(string $imageUrl): string
|
||||
{
|
||||
return "{$imageUrl}-h480";
|
||||
}
|
||||
|
||||
public static function imageMogr2H1080(string $imageUrl): string
|
||||
{
|
||||
return "{$imageUrl}-h1080";
|
||||
}
|
||||
}
|
||||
18
common/MobileHelper.php
Normal file
18
common/MobileHelper.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace app\common;
|
||||
|
||||
class MobileHelper
|
||||
{
|
||||
public static function isValidChinaMobile($remarkDict): bool
|
||||
{
|
||||
if (is_string($remarkDict) && $remarkDict) {
|
||||
$val = json_decode($remarkDict, true);
|
||||
if (isset($val['是否为隐私号']) && $val['是否为隐私号'] == '是') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
93
common/PaginationHelper.php
Normal file
93
common/PaginationHelper.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\common;
|
||||
|
||||
|
||||
use yii\data\ActiveDataProvider;
|
||||
use yii\data\ArrayDataProvider;
|
||||
use yii\db\ActiveQuery;
|
||||
use yii\db\QueryInterface;
|
||||
use yii\di\NotInstantiableException;
|
||||
|
||||
class PaginationHelper
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
* @param \yii\db\ActiveQuery $query
|
||||
* @param int $limit
|
||||
* @param string $pageParam
|
||||
* @param string $pageSizeParam
|
||||
*
|
||||
* @return \yii\data\ActiveDataProvider
|
||||
*/
|
||||
public static function createDataProvider($query, $limit = 10, $pageParam = 'page', $pageSizeParam = 'per_page')
|
||||
{
|
||||
$dataProvider = new ActiveDataProvider([
|
||||
'query' => $query,
|
||||
'pagination' => [
|
||||
//分页大小
|
||||
'pageSize' => \Yii::$app->request->get($pageSizeParam) ?: $limit,
|
||||
//设置地址栏当前页数参数名
|
||||
'pageParam' => $pageParam,
|
||||
//设置地址栏分页大小参数名
|
||||
'pageSizeParam' => $pageSizeParam,
|
||||
],
|
||||
]);
|
||||
|
||||
return $dataProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $query
|
||||
* @param int $limit
|
||||
* @param string $pageParam
|
||||
* @param string $pageSizeParam
|
||||
*
|
||||
* @return \yii\db\QueryInterface
|
||||
* @throws \yii\di\NotInstantiableException
|
||||
*/
|
||||
public static function createQueryProvider($query, $limit = 10, $pageParam = 'page', $pageSizeParam = 'limit')
|
||||
{
|
||||
$param = \Yii::$app->request->get();
|
||||
$pageSize = $param['limit'] ?: $param[$pageSizeParam];
|
||||
|
||||
if ($query instanceof QueryInterface) {
|
||||
return $query->limit($pageSize)->offset(($param[$pageParam] - 1) * $pageSize);
|
||||
}
|
||||
throw new NotInstantiableException('not instanceof QueryInterFace');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array
|
||||
* @param int $limit
|
||||
* @param string $pageParam
|
||||
* @param string $pageSizeParam
|
||||
* @param array $orderFields
|
||||
*
|
||||
* @return ArrayDataProvider
|
||||
*/
|
||||
public static function createArrayDataProvider($array = [], $orderFields = [], $limit = 10, $pageParam = 'page', $pageSizeParam = 'limit')
|
||||
{
|
||||
$attributes = array_keys($orderFields);
|
||||
$order = $orderFields;
|
||||
$dataProvider = new ArrayDataProvider([
|
||||
'allModels' => $array,
|
||||
'sort' => [
|
||||
'attributes' => $attributes,
|
||||
'defaultOrder' => $order
|
||||
],
|
||||
'pagination' => [
|
||||
//分页大小
|
||||
'pageSize' => \Yii::$app->request->get($pageSizeParam, 10),
|
||||
//设置地址栏当前页数参数名
|
||||
'pageParam' => $pageParam,
|
||||
//设置地址栏分页大小参数名
|
||||
'pageSizeParam' => $pageSizeParam,
|
||||
],
|
||||
]);
|
||||
|
||||
return $dataProvider;
|
||||
}
|
||||
}
|
||||
11
common/SaltHelper.php
Normal file
11
common/SaltHelper.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\common;
|
||||
|
||||
class SaltHelper
|
||||
{
|
||||
public static function addSalt(string $data)
|
||||
{
|
||||
return $data . \Yii::$app->params['salt'];
|
||||
}
|
||||
}
|
||||
106
components/Oceanengine.php
Normal file
106
components/Oceanengine.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace app\components;
|
||||
|
||||
use app\models\Oauth;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use yii\base\Component;
|
||||
|
||||
class Oceanengine extends Component
|
||||
{
|
||||
|
||||
const CACHE_KEY = 'oceanengine_cache';
|
||||
|
||||
public function getAccessToken($adminUid)
|
||||
{
|
||||
return Oauth::find()->where(['uid' => $adminUid])->one()->access_token;
|
||||
}
|
||||
|
||||
public function getRefreshToken($adminUid)
|
||||
{
|
||||
return Oauth::find()->where(['uid' => $adminUid])->one()->refresh_token;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 获取授权账号下的子账号
|
||||
* @param $adminAccountId
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdminChildAccount($adminAccountId): array
|
||||
{
|
||||
$client = new Client();
|
||||
$headers = [
|
||||
'Access-Token' => $this->getAccessToken($adminAccountId)
|
||||
];
|
||||
$request = new Request('GET',
|
||||
'https://api.oceanengine.com/open_api/oauth2/advertiser/get/',
|
||||
$headers);
|
||||
$res = $client->sendAsync($request)->wait();
|
||||
return json_decode($res->getBody(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地推用户
|
||||
*
|
||||
* @param $adminUid
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAccountLocal($adminUid): array
|
||||
{
|
||||
$client = new Client();
|
||||
$headers = [
|
||||
'Access-Token' => $this->getAccessToken($adminUid)
|
||||
];
|
||||
$request = new Request('GET', 'https://ad.oceanengine.com/open_api/2/customer_center/advertiser/list/?account_source=LOCAL&cc_account_id=1742303335399432&filtering=%7B%22account_name%22%3A%22%22%7D&page=1&page_size=100', $headers);
|
||||
$res = $client->sendAsync($request)->wait();
|
||||
return json_decode($res->getBody(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新的线索
|
||||
*/
|
||||
public function getClue($adminUid, array $accountId, $startTime, $endTime, $page = 1)
|
||||
{
|
||||
$client = new Client();
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json',
|
||||
'Access-Token' => $this->getAccessToken($adminUid)
|
||||
];
|
||||
$accounts = implode(',', $accountId);
|
||||
$body = '{
|
||||
"local_account_ids": [
|
||||
' . $accounts . '
|
||||
],
|
||||
"start_time": "' . $startTime . '",
|
||||
"end_time": "' . $endTime . '",
|
||||
"page": ' . $page . ',
|
||||
"page_size": 100
|
||||
}';
|
||||
$request = new Request('POST', 'https://api.oceanengine.com/open_api/2/tools/clue/life/get/', $headers, $body);
|
||||
$res = $client->sendAsync($request)->wait();
|
||||
|
||||
return json_decode($res->getBody(), true);
|
||||
}
|
||||
|
||||
public function refreshAccessToken($adminUid)
|
||||
{
|
||||
$client = new Client();
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json'
|
||||
];
|
||||
$body = '{
|
||||
"app_id": ' . \Yii::$app->params['app_id'] . ',
|
||||
"secret": "' . \Yii::$app->params['secret'] . '",
|
||||
"refresh_token": "' . $this->getRefreshToken($adminUid) . '"
|
||||
}';
|
||||
$request = new Request('POST',
|
||||
'https://api.oceanengine.com/open_api/oauth2/refresh_token/',
|
||||
$headers, $body);
|
||||
$res = $client->sendAsync($request)->wait();
|
||||
return json_decode($res->getBody(), true);
|
||||
}
|
||||
}
|
||||
19
components/serializers/Serializer.php
Normal file
19
components/serializers/Serializer.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace app\components\serializers;
|
||||
|
||||
class Serializer
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
public array $rules = []
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function serialize()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
82
composer.json
Normal file
82
composer.json
Normal file
@ -0,0 +1,82 @@
|
||||
{
|
||||
"name": "yiisoft/yii2-app-basic",
|
||||
"description": "Yii 2 Basic Project Template",
|
||||
"keywords": ["yii2", "framework", "basic", "project template"],
|
||||
"homepage": "https://www.yiiframework.com/",
|
||||
"type": "project",
|
||||
"license": "BSD-3-Clause",
|
||||
|
||||
"minimum-stability": "stable",
|
||||
|
||||
"require": {
|
||||
"php": ">=7.4.0",
|
||||
"yiisoft/yii2": "~2.0.45",
|
||||
"yiisoft/yii2-bootstrap5": "~2.0.2",
|
||||
"yiisoft/yii2-symfonymailer": "~2.0.3",
|
||||
"yidas/yii2-composer-bower-skip": "^2.0",
|
||||
"yidas/yii2-bower-asset": "^2.0",
|
||||
"guzzlehttp/guzzle": "^7.0",
|
||||
"omnilight/yii2-scheduling": "*",
|
||||
"yiisoft/yii2-queue": "^2.3",
|
||||
"yiisoft/yii2-redis": "^2.1",
|
||||
"qiniu/php-sdk": "^7.14"
|
||||
|
||||
},
|
||||
|
||||
"require-dev": {
|
||||
"yiisoft/yii2-debug": "~2.1.0",
|
||||
"yiisoft/yii2-gii": "~2.2.0",
|
||||
"yiisoft/yii2-faker": "~2.0.0",
|
||||
"codeception/codeception": "^5.0.0 || ^4.0",
|
||||
"codeception/lib-innerbrowser": "^4.0 || ^3.0 || ^1.1",
|
||||
"codeception/module-asserts": "^3.0 || ^1.1",
|
||||
"codeception/module-yii2": "^1.1",
|
||||
"codeception/module-filesystem": "^3.0 || ^2.0 || ^1.1",
|
||||
"codeception/verify": "^3.0 || ^2.2",
|
||||
"symfony/browser-kit": "^6.0 || >=2.7 <=4.2.4"
|
||||
},
|
||||
|
||||
"config": {
|
||||
"process-timeout": 1800,
|
||||
"allow-plugins": {
|
||||
"yiisoft/yii2-composer": true
|
||||
},
|
||||
"fxp-asset": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
|
||||
"scripts": {
|
||||
"post-install-cmd": [
|
||||
"yii\\composer\\Installer::postInstall"
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"yii\\composer\\Installer::postCreateProject",
|
||||
"yii\\composer\\Installer::postInstall"
|
||||
]
|
||||
},
|
||||
|
||||
"extra": {
|
||||
"yii\\composer\\Installer::postCreateProject": {
|
||||
"setPermission": [
|
||||
{
|
||||
"runtime": "0777",
|
||||
"web/assets": "0777",
|
||||
"yii": "0755"
|
||||
}
|
||||
]
|
||||
},
|
||||
"yii\\composer\\Installer::postInstall": {
|
||||
"generateCookieValidationKey": [
|
||||
"config/web.php"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://mirrors.cloud.tencent.com/composer/"
|
||||
}
|
||||
]
|
||||
}
|
||||
5100
composer.lock
generated
Normal file
5100
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
config/__autocomplete.php
Normal file
33
config/__autocomplete.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This class only exists here for IDE (PHPStorm/Netbeans/...) autocompletion.
|
||||
* This file is never included anywhere.
|
||||
* Adjust this file to match classes configured in your application config, to enable IDE autocompletion for custom components.
|
||||
* Example: A property phpdoc can be added in `__Application` class as `@property \vendor\package\Rollbar|__Rollbar $rollbar` and adding a class in this file
|
||||
* ```php
|
||||
* // @property of \vendor\package\Rollbar goes here
|
||||
* class __Rollbar {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class Yii {
|
||||
/**
|
||||
* @var \yii\web\Application|\yii\console\Application|__Application
|
||||
*/
|
||||
public static $app;
|
||||
}
|
||||
|
||||
/**
|
||||
* @property yii\rbac\DbManager $authManager
|
||||
* @property \yii\web\User|__WebUser $user
|
||||
*
|
||||
*/
|
||||
class __Application {
|
||||
}
|
||||
|
||||
/**
|
||||
* @property app\models\User $identity
|
||||
*/
|
||||
class __WebUser {
|
||||
}
|
||||
72
config/console.php
Normal file
72
config/console.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
$params = require __DIR__ . '/params.php';
|
||||
$db = require __DIR__ . '/db.php';
|
||||
|
||||
$config = [
|
||||
'id' => 'basic-console',
|
||||
'basePath' => dirname(__DIR__),
|
||||
'bootstrap' => ['log', 'queue'],
|
||||
'controllerNamespace' => 'app\commands',
|
||||
'timeZone' => 'Asia/Shanghai',
|
||||
'aliases' => [
|
||||
'@bower' => '@vendor/bower-asset',
|
||||
'@npm' => '@vendor/npm-asset',
|
||||
'@tests' => '@app/tests',
|
||||
],
|
||||
'components' => [
|
||||
'queue' => [
|
||||
'class' => \yii\queue\redis\Queue::class,
|
||||
'redis' => 'redis', // Redis 连接组件或其配置
|
||||
'channel' => 'queue', // 队列通道键
|
||||
'as log' => \yii\queue\LogBehavior::class, // 日志行为
|
||||
],
|
||||
'oceanengine' => [
|
||||
'class' => 'app\components\Oceanengine',
|
||||
],
|
||||
'redis' => [
|
||||
'class' => 'yii\redis\Connection',
|
||||
'hostname' => 'localhost',
|
||||
'port' => 6379,
|
||||
'database' => 0,
|
||||
],
|
||||
'cache' => [
|
||||
'class' => 'yii\caching\FileCache',
|
||||
],
|
||||
'log' => [
|
||||
'targets' => [
|
||||
[
|
||||
'class' => 'yii\log\FileTarget',
|
||||
'levels' => ['error', 'warning'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'db' => $db,
|
||||
],
|
||||
'params' => $params,
|
||||
/*
|
||||
'controllerMap' => [
|
||||
'fixture' => [ // Fixture generation command line.
|
||||
'class' => 'yii\faker\FixtureController',
|
||||
],
|
||||
],
|
||||
*/
|
||||
];
|
||||
|
||||
if (YII_ENV_DEV) {
|
||||
// configuration adjustments for 'dev' environment
|
||||
// $config['bootstrap'][] = 'gii';
|
||||
// $config['modules']['gii'] = [
|
||||
// 'class' => 'yii\gii\Module',
|
||||
// ];
|
||||
// configuration adjustments for 'dev' environment
|
||||
// requires version `2.1.21` of yii2-debug module
|
||||
$config['bootstrap'][] = 'debug';
|
||||
$config['modules']['debug'] = [
|
||||
'class' => 'yii\debug\Module',
|
||||
// uncomment the following to add your IP if you are not connecting from localhost.
|
||||
//'allowedIPs' => ['127.0.0.1', '::1'],
|
||||
];
|
||||
}
|
||||
|
||||
return $config;
|
||||
15
config/db.php
Normal file
15
config/db.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'class' => 'yii\db\Connection',
|
||||
'dsn' => 'mysql:host=localhost;dbname=23cm',
|
||||
'username' => 'root',
|
||||
'password' => '123456',
|
||||
'charset' => 'utf8',
|
||||
|
||||
// mysql_77BFSX
|
||||
// Schema cache options (for production environment)
|
||||
//'enableSchemaCache' => true,
|
||||
//'schemaCacheDuration' => 60,
|
||||
//'schemaCache' => 'cache',
|
||||
];
|
||||
14
config/params.php
Normal file
14
config/params.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'adminEmail' => 'admin@example.com',
|
||||
'senderEmail' => 'noreply@example.com',
|
||||
'senderName' => 'Example.com mailer',
|
||||
|
||||
'app_id' => '1852484891502756',
|
||||
'secret' => '06b3ab42e212f4824fa19d958512d025eb477fea',
|
||||
|
||||
'salt' => 'toom1996',
|
||||
|
||||
'cdnAddress' => 'http://static.23cm.cn/'
|
||||
];
|
||||
18
config/schedule.php
Normal file
18
config/schedule.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
/**
|
||||
* @var \omnilight\scheduling\Schedule $schedule
|
||||
*/
|
||||
|
||||
// /Users/toom/Documents/yii2/xiansuo_admin
|
||||
// php /www/wwwroot/sites/clue/index/clue/yii schedule/run --scheduleFile=/Users/toom/Documents/yii2/xiansuo_admin/config/schedule.php 1>> /dev/null 2>&1
|
||||
$path = Yii::getAlias('@runtime') . '/logs/' . date('Ym') . '/' . date('d') . '/';
|
||||
$h = date('H');
|
||||
$hi = date('H-i');
|
||||
\app\common\FileHelper::mkdirs($path);
|
||||
// php /www/wwwroot/sites/clue/index/clue/yii schedule/run --scheduleFile=/www/wwwroot/sites/clue/index/clue/config/schedule.php 1>> /dev/null 2>&1
|
||||
/** 8-22/每2小时执行一次 / 获取【小红书定制】文章信息 */
|
||||
$filePath = $path . 'clue_chain.log';
|
||||
$schedule->command('sync/pull-clue-chain')->cron('*/5 * * * * *')->appendOutputTo($filePath);
|
||||
|
||||
$filePath = $path . 'refresh_token.log';
|
||||
$schedule->command('sync/refresh-token')->cron('0 */12 * * * *')->appendOutputTo($filePath);
|
||||
46
config/test.php
Normal file
46
config/test.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
$params = require __DIR__ . '/params.php';
|
||||
$db = require __DIR__ . '/test_db.php';
|
||||
|
||||
/**
|
||||
* Application configuration shared by all test types
|
||||
*/
|
||||
return [
|
||||
'id' => 'basic-tests',
|
||||
'basePath' => dirname(__DIR__),
|
||||
'aliases' => [
|
||||
'@bower' => '@vendor/bower-asset',
|
||||
'@npm' => '@vendor/npm-asset',
|
||||
],
|
||||
'language' => 'en-US',
|
||||
'components' => [
|
||||
'db' => $db,
|
||||
'mailer' => [
|
||||
'class' => \yii\symfonymailer\Mailer::class,
|
||||
'viewPath' => '@app/mail',
|
||||
// send all mails to a file by default.
|
||||
'useFileTransport' => true,
|
||||
'messageClass' => 'yii\symfonymailer\Message'
|
||||
],
|
||||
'assetManager' => [
|
||||
'basePath' => __DIR__ . '/../web/assets',
|
||||
],
|
||||
'urlManager' => [
|
||||
'showScriptName' => true,
|
||||
],
|
||||
'user' => [
|
||||
'identityClass' => 'app\models\User',
|
||||
],
|
||||
'request' => [
|
||||
'cookieValidationKey' => 'test',
|
||||
'enableCsrfValidation' => false,
|
||||
// but if you absolutely need it set cookie domain to localhost
|
||||
/*
|
||||
'csrfCookie' => [
|
||||
'domain' => 'localhost',
|
||||
],
|
||||
*/
|
||||
],
|
||||
],
|
||||
'params' => $params,
|
||||
];
|
||||
6
config/test_db.php
Normal file
6
config/test_db.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
$db = require __DIR__ . '/db.php';
|
||||
// test database! Important not to run tests on production or development databases
|
||||
$db['dsn'] = 'mysql:host=localhost;dbname=yii2basic_test';
|
||||
|
||||
return $db;
|
||||
96
config/web.php
Normal file
96
config/web.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
$params = require __DIR__ . '/params.php';
|
||||
$db = require __DIR__ . '/db.php';
|
||||
|
||||
$config = [
|
||||
'id' => 'basic',
|
||||
'basePath' => dirname(__DIR__),
|
||||
'bootstrap' => ['log'],
|
||||
'aliases' => [
|
||||
'@bower' => '@vendor/yidas/yii2-bower-asset/bower',
|
||||
'@npm' => '@vendor/npm-asset',
|
||||
],
|
||||
'timeZone' => 'Asia/Shanghai',
|
||||
'components' => [
|
||||
'assetManager' => [
|
||||
// 'bundles' => [
|
||||
// 'yii\web\JqueryAsset' => [
|
||||
// 'basePath' => '@webroot',
|
||||
// 'baseUrl' => '@web',
|
||||
// 'js' => [
|
||||
// 'js/jquery.js', // 你的 jQuery
|
||||
// ],
|
||||
// ],
|
||||
// ],
|
||||
],
|
||||
'request' => [
|
||||
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
|
||||
'cookieValidationKey' => '0VfdiVtgFI4CJ4PNusreE5khkMp8fna2',
|
||||
],
|
||||
'cache' => [
|
||||
'class' => 'yii\caching\FileCache',
|
||||
],
|
||||
'oceanengine' => [
|
||||
'class' => 'app\components\Oceanengine',
|
||||
],
|
||||
'user' => [
|
||||
'identityClass' => 'app\models\User',
|
||||
'enableAutoLogin' => true,
|
||||
],
|
||||
'redis' => [
|
||||
'class' => 'yii\redis\Connection',
|
||||
'hostname' => 'localhost',
|
||||
'port' => 6379,
|
||||
'database' => 0,
|
||||
],
|
||||
'errorHandler' => [
|
||||
'errorAction' => 'site/error',
|
||||
],
|
||||
'mailer' => [
|
||||
'class' => \yii\symfonymailer\Mailer::class,
|
||||
'viewPath' => '@app/mail',
|
||||
// send all mails to a file by default.
|
||||
'useFileTransport' => true,
|
||||
],
|
||||
'log' => [
|
||||
'traceLevel' => YII_DEBUG ? 3 : 0,
|
||||
'targets' => [
|
||||
[
|
||||
'class' => 'yii\log\FileTarget',
|
||||
'levels' => ['error', 'warning'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'db' => $db,
|
||||
'urlManager' => [
|
||||
'enablePrettyUrl' => true,
|
||||
'showScriptName' => false,
|
||||
'rules' => [
|
||||
// '<controller:\w+>/<action:\w+>/<id:\d+>' => '<controller>/<action>', // 带 ID
|
||||
'<action:\w+>' => 'site/<action>', // site 控制器下的简单 action,例如 login, logout, console
|
||||
'' => 'site/index', // 首页
|
||||
],
|
||||
],
|
||||
],
|
||||
'params' => $params,
|
||||
];
|
||||
|
||||
if (YII_ENV_DEV) {
|
||||
// configuration adjustments for 'dev' environment
|
||||
// $config['bootstrap'][] = 'debug';
|
||||
// $config['modules']['debug'] = [
|
||||
// 'class' => 'yii\debug\Module',
|
||||
// // uncomment the following to add your IP if you are not connecting from localhost.
|
||||
// //'allowedIPs' => ['127.0.0.1', '::1'],
|
||||
// ];
|
||||
|
||||
$config['bootstrap'][] = 'gii';
|
||||
$config['modules']['gii'] = [
|
||||
'class' => 'yii\gii\Module',
|
||||
// uncomment the following to add your IP if you are not connecting from localhost.
|
||||
//'allowedIPs' => ['127.0.0.1', '::1'],
|
||||
];
|
||||
}
|
||||
|
||||
return $config;
|
||||
286
controllers/ApiController.php
Normal file
286
controllers/ApiController.php
Normal file
@ -0,0 +1,286 @@
|
||||
<?php
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use app\common\PaginationHelper;
|
||||
use app\models\Clue;
|
||||
use app\models\Oauth;
|
||||
use app\models\OauthAccount;
|
||||
use app\models\OauthAccountLocal;
|
||||
use app\models\UserAdvertiser;
|
||||
use app\models\Xiansuo;
|
||||
use http\Url;
|
||||
use Yii;
|
||||
use yii\data\Pagination;
|
||||
use yii\filters\AccessControl;
|
||||
use yii\helpers\ArrayHelper;
|
||||
use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
use yii\filters\VerbFilter;
|
||||
use app\models\LoginForm;
|
||||
use app\models\ContactForm;
|
||||
use yii\web\UrlManager;
|
||||
|
||||
class ApiController extends Controller
|
||||
{
|
||||
public function beforeAction($action)
|
||||
{
|
||||
parent::beforeAction($action);
|
||||
|
||||
if (Yii::$app->user->isGuest) {
|
||||
return $this->redirect('/login');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function actions()
|
||||
{
|
||||
return [
|
||||
'error' => [
|
||||
'class' => 'yii\web\ErrorAction',
|
||||
],
|
||||
'captcha' => [
|
||||
'class' => 'yii\captcha\CaptchaAction',
|
||||
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function actionXiansuo()
|
||||
{
|
||||
$query = Clue::find()->filterWhere(
|
||||
['like', 'telephone', Yii::$app->request->get('phone')],
|
||||
)->andFilterWhere(
|
||||
['like', 'name', Yii::$app->request->get('name')]
|
||||
)->andFilterWhere(
|
||||
['like', 'note', Yii::$app->request->get('note')]
|
||||
)->andFilterWhere(
|
||||
['convert_status' => Yii::$app->request->get('convert_status')]
|
||||
)->andFilterWhere(
|
||||
['like', 'auto_city_name', Yii::$app->request->get('city')]
|
||||
)->andFilterWhere(
|
||||
[ '>=', 'create_time_detail', Yii::$app->request->get('date_start')]
|
||||
)->andFilterWhere(
|
||||
[ '<=', 'create_time_detail', Yii::$app->request->get('date_end')]
|
||||
)->orderBy('create_time_detail DESC');
|
||||
$provider = PaginationHelper::createDataProvider($query, limit: Yii::$app->request->get('limit'));
|
||||
|
||||
$covertStatus = function ($status) {
|
||||
if ($status == 1) {
|
||||
return '合法转化';
|
||||
} elseif ($status == 2) {
|
||||
return '待确认';
|
||||
} elseif ($status == 3) {
|
||||
return '营销预览';
|
||||
} elseif ($status == 4) {
|
||||
return '其他转化';
|
||||
}
|
||||
};
|
||||
|
||||
foreach ($provider->getModels() as &$item) {
|
||||
$item['convert_status'] = $covertStatus($item['convert_status']);
|
||||
$item['telephone'] = ($item['is_virtual'] == 1 ? '【虚拟号码】' : '') . $item['telephone'];
|
||||
}
|
||||
return $this->asJson([
|
||||
'count' => $provider->totalCount,
|
||||
'code' => 0,
|
||||
'data' => $provider->models,
|
||||
'message' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
public function actionXiansuoPrivate()
|
||||
{
|
||||
$localAccountList = ArrayHelper::getColumn(UserAdvertiser::find()->where(['user_id' => Yii::$app->user->id, 'is_delete' => 0])->asArray()->all() ?: [], 'advertiser_id');
|
||||
|
||||
// var_dump($localAccountList);die;
|
||||
$query = Clue::find()->where([
|
||||
'local_account_id' => $localAccountList,
|
||||
'convert_status' => 1,
|
||||
'is_virtual' => 0
|
||||
])->andFilterWhere(
|
||||
['like', 'telephone', Yii::$app->request->get('phone')],
|
||||
)->andFilterWhere(
|
||||
['like', 'name', Yii::$app->request->get('name')]
|
||||
)->andFilterWhere(
|
||||
['like', 'note', Yii::$app->request->get('note')]
|
||||
)->andFilterWhere(
|
||||
['like', 'auto_city_name', Yii::$app->request->get('city')]
|
||||
)->andFilterWhere(
|
||||
[ '>=', 'create_time_detail', Yii::$app->request->get('date_start')]
|
||||
)->andFilterWhere(
|
||||
[ '<=', 'create_time_detail', Yii::$app->request->get('date_end')]
|
||||
)->orderBy('create_time_detail DESC');
|
||||
$x = clone $query;
|
||||
$provider = PaginationHelper::createDataProvider($query, limit: Yii::$app->request->get('limit'));
|
||||
|
||||
$covertStatus = function ($status) {
|
||||
if ($status == 1) {
|
||||
return '合法转化';
|
||||
} elseif ($status == 2) {
|
||||
return '待确认';
|
||||
} elseif ($status == 3) {
|
||||
return '营销预览';
|
||||
} elseif ($status == 4) {
|
||||
return '其他转化';
|
||||
}
|
||||
};
|
||||
|
||||
foreach ($provider->getModels() as &$item) {
|
||||
$item['convert_status'] = $covertStatus($item['convert_status']);
|
||||
}
|
||||
return $this->asJson([
|
||||
'count' => $provider->totalCount,
|
||||
'code' => 0,
|
||||
'data' => $provider->models,
|
||||
'message' => 'ok',
|
||||
'sql' => $x->createCommand()->getRawSql()
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @url api/oauth-manage
|
||||
* @return Response
|
||||
*/
|
||||
public function actionOauthManage()
|
||||
{
|
||||
$query = Oauth::find();
|
||||
$provider = PaginationHelper::createDataProvider($query);
|
||||
|
||||
foreach ($provider->getModels() as &$item) {
|
||||
$item['updated_at'] = date('Y-m-d H:i:s', $item['updated_at']);
|
||||
}
|
||||
return $this->asJson([
|
||||
'count' => $provider->totalCount,
|
||||
'code' => 0,
|
||||
'data' => $provider->models,
|
||||
'message' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url api/oauth-manage-config
|
||||
*/
|
||||
public function actionOauthManageConfig()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @url api/init-oauth-admin
|
||||
*/
|
||||
public function actionInitOauthAdmin()
|
||||
{
|
||||
$uid = Yii::$app->request->post('uid');
|
||||
$tr = Yii::$app->db->beginTransaction();
|
||||
// Yii::$app->oceanengine->
|
||||
// 取出授权的account
|
||||
$oauthAdmin = Oauth::find()->where(['uid' => $uid])->one();
|
||||
$oauthAdmin->is_init = 1;
|
||||
$oauthAdmin->save();
|
||||
// $accounts = json_decode($oauthAdmin->advertiser_ids, true);
|
||||
|
||||
// 清理之前所有授权的账户
|
||||
OauthAccount::updateAll(['admin_uid' => $uid], ['is_delete' => 1]);
|
||||
$accountList = Yii::$app->oceanengine->getAdminChildAccount($uid);
|
||||
|
||||
// 本地推账户
|
||||
OauthAccountLocal::updateAll(['is_delete' => 1], ['admin_uid' => $uid]);
|
||||
|
||||
// 更新account 数据
|
||||
foreach ($accountList['data']['list'] ?? [] as $account) {
|
||||
$accountQuery = OauthAccount::find()->where(['account_id' => $account['account_id']])->one() ?: new OauthAccount();
|
||||
$accountQuery->is_delete = 0;
|
||||
$accountQuery->admin_uid = $uid;
|
||||
$accountQuery->account_name = $account['account_name'];
|
||||
$accountQuery->account_id = strval($account['account_id']);
|
||||
$accountQuery->save();
|
||||
|
||||
$accountLocalList = Yii::$app->oceanengine->getAccountLocal($uid);
|
||||
foreach ($accountLocalList['data']['list'] ?? [] as $accountLocal) {
|
||||
$query = OauthAccountLocal::find()->where(['advertiser_id' => $accountLocal['advertiser_id']])->one() ?: new OauthAccountLocal();
|
||||
$query->is_delete = 0;
|
||||
$query->admin_uid = $uid;
|
||||
$query->account_id = strval($account['account_id']);
|
||||
$query->advertiser_name = strval($accountLocal['advertiser_name']);
|
||||
$query->advertiser_id = strval($accountLocal['advertiser_id']);
|
||||
$query->save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$tr->commit();
|
||||
return $this->asJson([
|
||||
'code' => 0,
|
||||
'data' => [],
|
||||
'message' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function actionUpdateClue()
|
||||
{
|
||||
$clueId = Yii::$app->request->post('clue_id');
|
||||
$note = Yii::$app->request->post('note');
|
||||
$name = Yii::$app->request->post('name');
|
||||
$convertStatus = Yii::$app->request->post('convert_status');
|
||||
|
||||
$query = Clue::find()->where(['clue_id' => $clueId])->one();
|
||||
$query->note = $note;
|
||||
$query->name = $name;
|
||||
if ($convertStatus) {
|
||||
$query->convert_status = $convertStatus;
|
||||
}
|
||||
|
||||
$query->save();
|
||||
|
||||
return $this->asJson([
|
||||
'code' => 0,
|
||||
'data' => [],
|
||||
'message' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
public function actionOauthAccountLocalUpdate()
|
||||
{
|
||||
$advIds = Yii::$app->request->post('advertiser_ids');
|
||||
if (count($advIds) > 50) {
|
||||
return $this->asJson([
|
||||
'code' => -1,
|
||||
'data' => [],
|
||||
'message' => '最多只支持50个账户。',
|
||||
]);
|
||||
}
|
||||
|
||||
$isDelete = [];
|
||||
foreach ($advIds as $advVal) {
|
||||
list($id, $accountId) = explode('|', $advVal);
|
||||
if (!isset($isDelete[$accountId])) {
|
||||
OauthAccountLocal::updateAll(['is_active' => 0], ['account_id' => $accountId]);
|
||||
$isDelete[$accountId] = true;
|
||||
}
|
||||
|
||||
$query = OauthAccountLocal::find()->where(['id' => $id])->one();
|
||||
$query->is_active = 1;
|
||||
$query->save();
|
||||
}
|
||||
|
||||
return $this->asJson([
|
||||
'code' => 0,
|
||||
'data' => [],
|
||||
'message' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
11
controllers/BaseController.php
Normal file
11
controllers/BaseController.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
|
||||
use yii\web\Response;
|
||||
|
||||
class BaseController extends \yii\web\Controller
|
||||
{
|
||||
|
||||
}
|
||||
316
controllers/SiteController.php
Normal file
316
controllers/SiteController.php
Normal file
@ -0,0 +1,316 @@
|
||||
<?php
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use app\models\Clue;
|
||||
use app\models\Oauth;
|
||||
use app\models\OauthAccount;
|
||||
use app\models\OauthAccountLocal;
|
||||
use app\models\User;
|
||||
use app\models\UserAdvertiser;
|
||||
use http\Client;
|
||||
use Yii;
|
||||
use yii\filters\AccessControl;
|
||||
use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
use yii\filters\VerbFilter;
|
||||
use app\models\LoginForm;
|
||||
use app\models\ContactForm;
|
||||
use yii\web\UrlManager;
|
||||
|
||||
class SiteController extends Controller
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function behaviors()
|
||||
{
|
||||
return [
|
||||
'access' => [
|
||||
'class' => AccessControl::class,
|
||||
'only' => ['logout'],
|
||||
'rules' => [
|
||||
[
|
||||
'actions' => ['logout'],
|
||||
'allow' => true,
|
||||
'roles' => ['@'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'verbs' => [
|
||||
'class' => VerbFilter::class,
|
||||
'actions' => [
|
||||
'logout' => ['get'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function actions()
|
||||
{
|
||||
return [
|
||||
'error' => [
|
||||
'class' => 'yii\web\ErrorAction',
|
||||
],
|
||||
'captcha' => [
|
||||
'class' => 'yii\captcha\CaptchaAction',
|
||||
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeAction($action)
|
||||
{
|
||||
parent::beforeAction($action);
|
||||
if ($action->id == 'login') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Yii::$app->user->isGuest) {
|
||||
return $this->redirect('/login');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays homepage.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function actionIndex()
|
||||
{
|
||||
$this->layout = 'main_index';
|
||||
if (Yii::$app->user->isGuest) {
|
||||
return $this->redirect(['site/login']);
|
||||
}
|
||||
|
||||
return $this->render('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Login action.
|
||||
*
|
||||
* @return Response|string
|
||||
*/
|
||||
public function actionLogin()
|
||||
{
|
||||
if (!Yii::$app->user->isGuest) {
|
||||
return $this->goHome();
|
||||
}
|
||||
|
||||
$model = new LoginForm();
|
||||
if ($model->load(Yii::$app->request->post()) && $model->login()) {
|
||||
return $this->goBack();
|
||||
}
|
||||
|
||||
$model->password = '';
|
||||
return $this->render('login', [
|
||||
'model' => $model,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout action.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function actionLogout()
|
||||
{
|
||||
Yii::$app->user->logout();
|
||||
|
||||
return $this->goHome();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays contact page.
|
||||
*
|
||||
* @return Response|string
|
||||
*/
|
||||
public function actionContact()
|
||||
{
|
||||
$model = new ContactForm();
|
||||
if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) {
|
||||
Yii::$app->session->setFlash('contactFormSubmitted');
|
||||
|
||||
return $this->refresh();
|
||||
}
|
||||
return $this->render('contact', [
|
||||
'model' => $model,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays about page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function actionAbout()
|
||||
{
|
||||
return $this->render('about');
|
||||
}
|
||||
|
||||
public function actionConsole()
|
||||
{
|
||||
$this->layout = 'main_index';
|
||||
return $this->render('console');
|
||||
}
|
||||
|
||||
public function actionXiansuo()
|
||||
{
|
||||
$this->layout = 'main_index';
|
||||
return $this->render('xiansuo');
|
||||
}
|
||||
|
||||
public function actionUsers()
|
||||
{
|
||||
$this->layout = 'main_index';
|
||||
return $this->render('users');
|
||||
}
|
||||
|
||||
public function actionUsercreate()
|
||||
{
|
||||
$this->layout = 'main_index';
|
||||
return $this->render('user/create');
|
||||
}
|
||||
|
||||
public function actionUseredit()
|
||||
{
|
||||
$id = Yii::$app->request->get('id');
|
||||
$this->layout = 'main_index';
|
||||
$advertiser = UserAdvertiser::find()->where([
|
||||
'user_id' => $id,
|
||||
'is_delete' => 0
|
||||
])->indexBy('advertiser_id')->asArray()->all() ?: [];
|
||||
|
||||
$allLocalAccount = OauthAccountLocal::find()->asArray()->all();
|
||||
$advertiserList = [];
|
||||
foreach ($allLocalAccount as $item) {
|
||||
$advertiserList[] = [
|
||||
'name' => $item['advertiser_name'],
|
||||
'value' => $item['id'],
|
||||
'selected' => isset($advertiser[$item['advertiser_id']])
|
||||
];
|
||||
}
|
||||
|
||||
return $this->render('user/edit', [
|
||||
'advertiser' => $advertiserList,
|
||||
'user_id' => $id
|
||||
]);
|
||||
}
|
||||
|
||||
public function actionUsereditpasswprd()
|
||||
{
|
||||
$id = Yii::$app->request->get('id');
|
||||
$query = User::find()->where(['id' => $id])->asArray()->one();
|
||||
$this->layout = 'main_index';
|
||||
return $this->render('user/edit-password', [
|
||||
'query' => $query
|
||||
]);
|
||||
}
|
||||
|
||||
public function actionOauth()
|
||||
{
|
||||
// http://j56ff926.natappfree.cc/?app_id=1852484891502756&auth_code=e7eb2c40cc7ebe38e359b283701e9406c3f1a382&material_auth_status=1&scope=%5B10000000%2C200000032%2C2%2C3%2C4%2C5%2C300000006%2C300000040%2C300000041%2C130%2C14%2C112%2C300000052%2C110%2C120%2C122%2C123%2C124%2C300000029%2C300000000%2C100000005%5D&state=your_custom_params&uid=4121395460312552
|
||||
$request = Yii::$app->request->get();
|
||||
$appId = $request['app_id'];
|
||||
$authCode = $request['auth_code'];
|
||||
$materialAuthStatus = $request['material_auth_status'];
|
||||
$scope = $request['scope'];
|
||||
$uid = $request['uid'];
|
||||
|
||||
$oauth = Oauth::find()->where(['uid' => $uid])->one();
|
||||
if (!$oauth) {
|
||||
$oauth = new Oauth();
|
||||
}
|
||||
$oauth->app_id = $appId;
|
||||
$oauth->auth_code = $authCode;
|
||||
$oauth->material_auth_status = $materialAuthStatus;
|
||||
$oauth->scope = $scope;
|
||||
$oauth->uid = $uid;
|
||||
$oauth->save();
|
||||
|
||||
$curl = new \app\common\CurlApp();
|
||||
$curl->setMethod();
|
||||
$curl->setUrl('https://ad.oceanengine.com/open_api/oauth2/access_token/');
|
||||
$curl->setPostData([
|
||||
"app_id" => Yii::$app->params['app_id'],
|
||||
"secret" => Yii::$app->params['secret'],
|
||||
"auth_code" => $authCode
|
||||
]);
|
||||
|
||||
$res = json_decode($curl->exec(), true);
|
||||
|
||||
if ($res['code'] != '0') {
|
||||
throw new \Exception($res['message']);
|
||||
}
|
||||
|
||||
$oauth->advertiser_ids = json_encode($res['data']['advertiser_ids']);
|
||||
$oauth->access_token = $res['data']['access_token'];
|
||||
$oauth->refresh_token = $res['data']['refresh_token'];
|
||||
$oauth->save();
|
||||
|
||||
echo '授权成功, 请关闭此页面';die;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function actionOauthmanage()
|
||||
{
|
||||
// TODO: 管理员验证
|
||||
|
||||
$this->layout = 'main_index';
|
||||
return $this->render('oauth-manage');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @url oauthmanageconfig
|
||||
* @return string
|
||||
*/
|
||||
public function actionOauthmanageconfig()
|
||||
{
|
||||
// TODO: 管理员验证
|
||||
|
||||
$this->layout = 'main_index';
|
||||
$adminUid = Yii::$app->request->get('uid');
|
||||
|
||||
$arr = [];
|
||||
$accounts = OauthAccount::find()->where(['admin_uid' => $adminUid, 'is_delete' => 0])->all();
|
||||
foreach ($accounts as $account) {
|
||||
$arr[$account['account_name']] = [
|
||||
'id' => $account['account_id'],
|
||||
'items' => OauthAccountLocal::find()->where(['account_id' => $account['account_id'], 'is_delete' => 0])->asArray()->all()
|
||||
];
|
||||
}
|
||||
|
||||
return $this->render('oauth-manage-config', [
|
||||
'arr' => $arr
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function actionGenjin()
|
||||
{
|
||||
$clueId = Yii::$app->request->get('clue_id');
|
||||
$note = Clue::find()->where(['clue_id' => $clueId])->one();
|
||||
$this->layout = 'main_index';
|
||||
return $this->render('genjin', [
|
||||
'clueId' => $clueId,
|
||||
'note' => $note->note,
|
||||
'name' => $note->name,
|
||||
'covert_status' => $note->convert_status
|
||||
]);
|
||||
}
|
||||
|
||||
public function actionPrivate()
|
||||
{
|
||||
$this->layout = 'main_index';
|
||||
return $this->render('xiansuo_private');
|
||||
}
|
||||
}
|
||||
344
controllers/api/UserController.php
Normal file
344
controllers/api/UserController.php
Normal file
@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
namespace app\controllers\api;
|
||||
|
||||
use app\common\PaginationHelper;
|
||||
use app\models\Clue;
|
||||
use app\models\Oauth;
|
||||
use app\models\OauthAccount;
|
||||
use app\models\OauthAccountLocal;
|
||||
use app\models\User;
|
||||
use app\models\UserAdvertiser;
|
||||
use app\models\Xiansuo;
|
||||
use http\Url;
|
||||
use Yii;
|
||||
use yii\base\DynamicModel;
|
||||
use yii\data\Pagination;
|
||||
use yii\filters\AccessControl;
|
||||
use yii\helpers\ArrayHelper;
|
||||
use yii\validators\Validator;
|
||||
use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
use yii\filters\VerbFilter;
|
||||
use app\models\LoginForm;
|
||||
use app\models\ContactForm;
|
||||
use yii\web\UrlManager;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function beforeAction($action)
|
||||
{
|
||||
parent::beforeAction($action);
|
||||
|
||||
if (Yii::$app->user->isGuest) {
|
||||
return $this->redirect('/login');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function actions()
|
||||
{
|
||||
return [
|
||||
'error' => [
|
||||
'class' => 'yii\web\ErrorAction',
|
||||
],
|
||||
'captcha' => [
|
||||
'class' => 'yii\captcha\CaptchaAction',
|
||||
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function actionIndex()
|
||||
{
|
||||
$query = User::find();
|
||||
$provider = PaginationHelper::createDataProvider($query);
|
||||
|
||||
$models = $provider->getModels();
|
||||
foreach ($models as &$item) {
|
||||
$item = $item->toArray();
|
||||
$item['created_at'] = date('Y-m-d H:i:s', $item['updated_at']);
|
||||
$localAccountList = ArrayHelper::getColumn(UserAdvertiser::find()->where(['is_delete' => 0, 'user_id' => $item])->asArray()->all(), 'advertiser_name');
|
||||
$item['advertiser_status'] = implode(',', $localAccountList);
|
||||
}
|
||||
|
||||
return $this->asJson([
|
||||
'count' => $provider->totalCount,
|
||||
'code' => 0,
|
||||
'data' => $models,
|
||||
'message' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
public function actionCreate()
|
||||
{
|
||||
$model = DynamicModel::validateData(Yii::$app->request->post(), [
|
||||
// ===== 用户名 =====
|
||||
[['username'], 'required'],
|
||||
[['username'], 'trim'],
|
||||
[['username'], 'string', 'min' => 4, 'max' => 20],
|
||||
[['username'], 'match', 'pattern' => '/^[a-zA-Z0-9_]+$/', 'message' => '账号只能包含字母、数字和下划线'],
|
||||
[['username'], 'unique', 'targetClass' => User::class, 'message' => '该账号已存在'],
|
||||
|
||||
// ===== 密码 =====
|
||||
[['password'], 'required'],
|
||||
[['password'], 'string', 'min' => 6, 'max' => 32],
|
||||
[['password'], 'match',
|
||||
'pattern' => '/^(?=.*[A-Za-z])(?=.*\d).+$/',
|
||||
'message' => '密码必须包含字母和数字'
|
||||
],
|
||||
]);
|
||||
|
||||
if ($model->hasErrors()) {
|
||||
return $this->asJson([
|
||||
'code' => 1,
|
||||
'msg' => current($model->getFirstErrors())
|
||||
]);
|
||||
}
|
||||
$username = Yii::$app->request->post('username');
|
||||
$password = Yii::$app->request->post('password');
|
||||
$model = new User();
|
||||
$model->username = $username;
|
||||
$model->auth_key = \Yii::$app->security->generateRandomString();
|
||||
$model->password_hash = \Yii::$app->security->generatePasswordHash($password);
|
||||
$model->email = "{$username}.com";
|
||||
$model->role = 'USER';
|
||||
$model->created_at = time();
|
||||
$model->updated_at = time();
|
||||
$model->save();
|
||||
// var_dump($model->errors);
|
||||
return $this->asJson([
|
||||
'code' => 0,
|
||||
'message' => 'ok'
|
||||
]);
|
||||
}
|
||||
|
||||
public function actionUpdate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function actionDelete()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function actionXiansuo()
|
||||
{
|
||||
$query = Clue::find()->filterWhere(
|
||||
['like', 'telephone', Yii::$app->request->get('phone')],
|
||||
)->andFilterWhere(
|
||||
['like', 'name', Yii::$app->request->get('name')]
|
||||
)->andFilterWhere(
|
||||
['like', 'note', Yii::$app->request->get('note')]
|
||||
)->orderBy('id DESC');
|
||||
$provider = PaginationHelper::createDataProvider($query, limit: Yii::$app->request->get('limit'));
|
||||
|
||||
return $this->asJson([
|
||||
'count' => $provider->totalCount,
|
||||
'code' => 0,
|
||||
'data' => $provider->models,
|
||||
'message' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @url api/oauth-manage
|
||||
* @return Response
|
||||
*/
|
||||
public function actionOauthManage()
|
||||
{
|
||||
$query = Oauth::find();
|
||||
$provider = PaginationHelper::createDataProvider($query);
|
||||
|
||||
foreach ($provider->getModels() as &$item) {
|
||||
$item['updated_at'] = date('Y-m-d H:i:s', $item['updated_at']);
|
||||
}
|
||||
return $this->asJson([
|
||||
'count' => $provider->totalCount,
|
||||
'code' => 0,
|
||||
'data' => $provider->models,
|
||||
'message' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url api/oauth-manage-config
|
||||
*/
|
||||
public function actionOauthManageConfig()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @url api/init-oauth-admin
|
||||
*/
|
||||
public function actionInitOauthAdmin()
|
||||
{
|
||||
$uid = Yii::$app->request->post('uid');
|
||||
$tr = Yii::$app->db->beginTransaction();
|
||||
// Yii::$app->oceanengine->
|
||||
// 取出授权的account
|
||||
$oauthAdmin = Oauth::find()->where(['uid' => $uid])->one();
|
||||
$oauthAdmin->is_init = 1;
|
||||
$oauthAdmin->save();
|
||||
// $accounts = json_decode($oauthAdmin->advertiser_ids, true);
|
||||
|
||||
// 清理之前所有授权的账户
|
||||
OauthAccount::updateAll(['admin_uid' => $uid], ['is_delete' => 1]);
|
||||
$accountList = Yii::$app->oceanengine->getAdminChildAccount($uid);
|
||||
|
||||
// 本地推账户
|
||||
OauthAccountLocal::updateAll(['is_delete' => 1], ['admin_uid' => $uid]);
|
||||
|
||||
// 更新account 数据
|
||||
foreach ($accountList['data']['list'] ?? [] as $account) {
|
||||
$accountQuery = OauthAccount::find()->where(['account_id' => $account['account_id']])->one() ?: new OauthAccount();
|
||||
$accountQuery->is_delete = 0;
|
||||
$accountQuery->admin_uid = $uid;
|
||||
$accountQuery->account_name = $account['account_name'];
|
||||
$accountQuery->account_id = strval($account['account_id']);
|
||||
$accountQuery->save();
|
||||
|
||||
$accountLocalList = Yii::$app->oceanengine->getAccountLocal($uid);
|
||||
foreach ($accountLocalList['data']['list'] ?? [] as $accountLocal) {
|
||||
$query = OauthAccountLocal::find()->where(['advertiser_id' => $accountLocal['advertiser_id']])->one() ?: new OauthAccountLocal();
|
||||
$query->is_delete = 0;
|
||||
$query->admin_uid = $uid;
|
||||
$query->account_id = strval($account['account_id']);
|
||||
$query->advertiser_name = strval($accountLocal['advertiser_name']);
|
||||
$query->advertiser_id = strval($accountLocal['advertiser_id']);
|
||||
$query->save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$tr->commit();
|
||||
return $this->asJson([
|
||||
'code' => 0,
|
||||
'data' => [],
|
||||
'message' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function actionUpdateClue()
|
||||
{
|
||||
$clueId = Yii::$app->request->post('clue_id');
|
||||
$note = Yii::$app->request->post('note');
|
||||
$name = Yii::$app->request->post('name');
|
||||
|
||||
$query = Clue::find()->where(['clue_id' => $clueId])->one();
|
||||
$query->note = $note;
|
||||
$query->name = $name;
|
||||
$query->save();
|
||||
|
||||
return $this->asJson([
|
||||
'code' => 0,
|
||||
'data' => [],
|
||||
'message' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
public function actionOauthAccountLocalUpdate()
|
||||
{
|
||||
$advIds = Yii::$app->request->post('advertiser_ids');
|
||||
|
||||
$isDelete = [];
|
||||
foreach ($advIds as $advVal) {
|
||||
list($id, $accountId) = explode('|', $advVal);
|
||||
if (!isset($isDelete[$accountId])) {
|
||||
OauthAccountLocal::updateAll(['is_active' => 0], ['account_id' => $accountId]);
|
||||
$isDelete[$accountId] = true;
|
||||
}
|
||||
|
||||
$query = OauthAccountLocal::find()->where(['id' => $id])->one();
|
||||
$query->is_active = 1;
|
||||
$query->save();
|
||||
}
|
||||
|
||||
return $this->asJson([
|
||||
'code' => 0,
|
||||
'data' => [],
|
||||
'message' => 'ok',
|
||||
]);
|
||||
}
|
||||
|
||||
public function actionResetPassword()
|
||||
{
|
||||
$password = Yii::$app->request->post('password');
|
||||
$id = Yii::$app->request->post('id');
|
||||
$model = User::find()->where(['id' => $id])->one();
|
||||
$model->auth_key = \Yii::$app->security->generateRandomString();
|
||||
$model->password_hash = \Yii::$app->security->generatePasswordHash($password);
|
||||
|
||||
return $this->asJson([
|
||||
'code' => 0,
|
||||
'data' => [],
|
||||
'message' => $model->save() ? '修改成功' : '修改失败',
|
||||
]);
|
||||
}
|
||||
|
||||
// public function actionEdit()
|
||||
// {
|
||||
// $password = Yii::$app->request->post('password');
|
||||
// $id = Yii::$app->request->post('id');
|
||||
// $model = User::find()->where(['id' => $id])->one();
|
||||
// $model->auth_key = \Yii::$app->security->generateRandomString();
|
||||
// $model->password_hash = \Yii::$app->security->generatePasswordHash($password);
|
||||
//
|
||||
// return $this->asJson([
|
||||
// 'code' => 0,
|
||||
// 'data' => [],
|
||||
// 'message' => $model->save() ? '修改成功' : '修改失败',
|
||||
// ]);
|
||||
// }
|
||||
|
||||
public function actionUpdateUser()
|
||||
{
|
||||
$userId = Yii::$app->request->post('user_id');
|
||||
$select = Yii::$app->request->post('select', '');
|
||||
UserAdvertiser::updateAll(['is_delete' => 1], ['user_id' => $userId]);
|
||||
|
||||
try {
|
||||
$select = explode(',', $select);
|
||||
} catch (\Throwable $exception) {
|
||||
$select = [];
|
||||
}
|
||||
|
||||
foreach ($select as $item) {
|
||||
$query = UserAdvertiser::find()->where(['user_id' => $userId, 'local_id' => $item])->one();
|
||||
if ($query) {
|
||||
$query->is_delete = 0;
|
||||
} else {
|
||||
$model = OauthAccountLocal::find()->where(['id' => $item])->one();
|
||||
$query = new UserAdvertiser();
|
||||
$query->advertiser_id = $model->advertiser_id;
|
||||
$query->user_id = $userId;
|
||||
$query->local_id = $userId;
|
||||
$query->advertiser_name = $model->advertiser_name;
|
||||
}
|
||||
|
||||
$query->save();
|
||||
}
|
||||
|
||||
return $this->asJson([
|
||||
'code' => 0,
|
||||
'data' => [],
|
||||
'message' => 'ok',
|
||||
]);
|
||||
}
|
||||
}
|
||||
26
controllers/api/v1/IndexController.php
Normal file
26
controllers/api/v1/IndexController.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace app\controllers\api\v1;
|
||||
|
||||
use app\controllers\BaseController;
|
||||
use app\models\Brand;
|
||||
use app\models\BrandRunway;
|
||||
use yii\web\Controller;
|
||||
|
||||
class IndexController extends BaseController
|
||||
{
|
||||
public function actionMain()
|
||||
{
|
||||
|
||||
$model = BrandRunway::find()->where(['is_deleted' => 0])->limit(2)->asArray()->all();
|
||||
|
||||
foreach ($model as &$item) {
|
||||
$item['cover'] = 'http://static.23cm.cn/11133613f321ed5eae0dc597f3451cae/37fdd980e520e8632f271730a30e88fc-h480';
|
||||
$item['brand_name'] = Brand::findOne($item['brand_id'])->name;
|
||||
}
|
||||
return $this->asJson([
|
||||
'code' => 0,
|
||||
'data' => $model
|
||||
]);
|
||||
}
|
||||
}
|
||||
39
controllers/api/v1/RunwayController.php
Normal file
39
controllers/api/v1/RunwayController.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace app\controllers\api\v1;
|
||||
|
||||
use app\common\ImageHelper;
|
||||
use app\components\serializers\Serializer;
|
||||
use app\controllers\BaseController;
|
||||
use app\models\Brand;
|
||||
use app\models\BrandRunway;
|
||||
use app\models\BrandRunwayImages;
|
||||
use yii\helpers\ArrayHelper;
|
||||
use yii\web\Controller;
|
||||
|
||||
class RunwayController extends BaseController
|
||||
{
|
||||
|
||||
public function actionView()
|
||||
{
|
||||
|
||||
$runwayId = \Yii::$app->request->get('id');
|
||||
|
||||
$runway = BrandRunway::find()->where(['id' => $runwayId])->asArray()->one();
|
||||
$runway['brand_name'] = Brand::findOne($runway['brand_id'])->name;
|
||||
$runwayImages = BrandRunwayImages::find()->where(['runway_id' => $runway['id']])->asArray()->all();
|
||||
|
||||
foreach ($runwayImages as &$image) {
|
||||
$image['s'] = ImageHelper::imageMogr2H480(\Yii::$app->params['cdnAddress'] . $image['name']);
|
||||
$image['xl'] = ImageHelper::imageMogr2H1080(\Yii::$app->params['cdnAddress'] . $image['name']);
|
||||
}
|
||||
|
||||
return $this->asJson([
|
||||
'code' => 0,
|
||||
'data' => [
|
||||
'info' => $runway,
|
||||
'images' => $runwayImages
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
9
docker-compose.yml
Normal file
9
docker-compose.yml
Normal file
@ -0,0 +1,9 @@
|
||||
version: '2'
|
||||
services:
|
||||
php:
|
||||
image: yiisoftware/yii2-php:7.4-apache
|
||||
volumes:
|
||||
- ~/.composer-docker/cache:/root/.composer/cache:delegated
|
||||
- ./:/app:delegated
|
||||
ports:
|
||||
- '8000:80'
|
||||
22
enums/SourceEnum.php
Normal file
22
enums/SourceEnum.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace app\enums;
|
||||
|
||||
enum SourceEnum
|
||||
{
|
||||
case VOGEU;
|
||||
|
||||
public function baseUrl(): string
|
||||
{
|
||||
return match($this) {
|
||||
self::VOGEU => 'https://www.vogue.com/',
|
||||
};
|
||||
}
|
||||
|
||||
public function value(): int
|
||||
{
|
||||
return match($this) {
|
||||
self::VOGEU => 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
22
mail/layouts/html.php
Normal file
22
mail/layouts/html.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
use yii\helpers\Html;
|
||||
|
||||
/** @var \yii\web\View $this view component instance */
|
||||
/** @var \yii\mail\MessageInterface $message the message being composed */
|
||||
/** @var string $content main view render result */
|
||||
?>
|
||||
<?php $this->beginPage() ?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=<?= Yii::$app->charset ?>" />
|
||||
<title><?= Html::encode($this->title) ?></title>
|
||||
<?php $this->head() ?>
|
||||
</head>
|
||||
<body>
|
||||
<?php $this->beginBody() ?>
|
||||
<?= $content ?>
|
||||
<?php $this->endBody() ?>
|
||||
</body>
|
||||
</html>
|
||||
<?php $this->endPage() ?>
|
||||
13
mail/layouts/text.php
Normal file
13
mail/layouts/text.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @var yii\web\View $this view component instance
|
||||
* @var yii\mail\BaseMessage $message the message being composed
|
||||
* @var string $content main view render result
|
||||
*/
|
||||
|
||||
$this->beginPage();
|
||||
$this->beginBody();
|
||||
echo $content;
|
||||
$this->endBody();
|
||||
$this->endPage();
|
||||
16
models/BaseModel.php
Normal file
16
models/BaseModel.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use yii\behaviors\TimestampBehavior;
|
||||
use yii\db\ActiveRecord;
|
||||
|
||||
class BaseModel extends ActiveRecord
|
||||
{
|
||||
public function behaviors()
|
||||
{
|
||||
return [
|
||||
TimestampBehavior::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
56
models/Brand.php
Normal file
56
models/Brand.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* This is the model class for table "brand".
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $created_at
|
||||
* @property int|null $updated_at
|
||||
* @property string|null $name
|
||||
* @property string|null $show_name
|
||||
* @property int|null $is_deleted
|
||||
*/
|
||||
class Brand extends BaseModel
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return 'brand';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['name', 'show_name'], 'default', 'value' => null],
|
||||
[['is_deleted'], 'default', 'value' => 0],
|
||||
[['created_at', 'updated_at', 'is_deleted'], 'integer'],
|
||||
[['name', 'show_name'], 'string', 'max' => 255],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'created_at' => 'Created At',
|
||||
'updated_at' => 'Updated At',
|
||||
'name' => 'Name',
|
||||
'show_name' => 'Show Name',
|
||||
'is_deleted' => 'Is Deleted',
|
||||
];
|
||||
}
|
||||
}
|
||||
58
models/BrandAlias.php
Normal file
58
models/BrandAlias.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* This is the model class for table "brand_alias".
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $created_at
|
||||
* @property int|null $updated_at
|
||||
* @property string|null $name
|
||||
* @property int $brand_id
|
||||
* @property int|null $is_deleted
|
||||
*/
|
||||
class BrandAlias extends BaseModel
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return 'brand_alias';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['name'], 'default', 'value' => null],
|
||||
[['is_deleted'], 'default', 'value' => 0],
|
||||
[['created_at', 'updated_at', 'brand_id', 'is_deleted'], 'integer'],
|
||||
[['brand_id'], 'required'],
|
||||
[['name'], 'string', 'max' => 255],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'created_at' => 'Created At',
|
||||
'updated_at' => 'Updated At',
|
||||
'name' => 'Name',
|
||||
'brand_id' => 'Brand ID',
|
||||
'is_deleted' => 'Is Deleted',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
68
models/BrandRunway.php
Normal file
68
models/BrandRunway.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* This is the model class for table "brand_runway".
|
||||
*
|
||||
* @property int $id
|
||||
* @property string|null $title
|
||||
* @property string|null $description
|
||||
* @property int|null $created_at
|
||||
* @property int|null $updated_at
|
||||
* @property int|null $is_deleted
|
||||
* @property int|null $image_count
|
||||
* @property int $brand_id
|
||||
* @property int|null $year
|
||||
* @property string|null $cover
|
||||
* @property string|null $source_url
|
||||
*/
|
||||
class BrandRunway extends \app\models\BaseModel
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return 'brand_runway';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['source_url'], 'default', 'value' => ''],
|
||||
[['year'], 'default', 'value' => 0],
|
||||
[['created_at', 'updated_at', 'is_deleted', 'image_count', 'brand_id', 'year'], 'integer'],
|
||||
[['title', 'cover', 'source_url'], 'string', 'max' => 255],
|
||||
[['description'], 'string', 'max' => 1024],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'title' => 'Title',
|
||||
'description' => 'Description',
|
||||
'created_at' => 'Created At',
|
||||
'updated_at' => 'Updated At',
|
||||
'is_deleted' => 'Is Deleted',
|
||||
'image_count' => 'Image Count',
|
||||
'brand_id' => 'Brand ID',
|
||||
'year' => 'Year',
|
||||
'cover' => 'Cover',
|
||||
'source_url' => 'Source Url',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
61
models/BrandRunwayImages.php
Normal file
61
models/BrandRunwayImages.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* This is the model class for table "brand_runway_images".
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $created_at
|
||||
* @property int|null $updated_at
|
||||
* @property int|null $is_deleted
|
||||
* @property string|null $image
|
||||
* @property int $runway_id
|
||||
* @property int $brand_id
|
||||
* @property string|null $name
|
||||
*/
|
||||
class BrandRunwayImages extends \app\models\BaseModel
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return 'brand_runway_images';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['brand_id'], 'default', 'value' => 0],
|
||||
[['name'], 'default', 'value' => ''],
|
||||
[['created_at', 'updated_at', 'is_deleted', 'runway_id', 'brand_id'], 'integer'],
|
||||
[['image', 'name'], 'string', 'max' => 255],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'created_at' => 'Created At',
|
||||
'updated_at' => 'Updated At',
|
||||
'is_deleted' => 'Is Deleted',
|
||||
'image' => 'Image',
|
||||
'runway_id' => 'Runway ID',
|
||||
'brand_id' => 'Brand ID',
|
||||
'name' => 'Name',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
60
models/BrandSource.php
Normal file
60
models/BrandSource.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* This is the model class for table "brand_source".
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $source
|
||||
* @property int|null $created_at
|
||||
* @property int|null $updated_at
|
||||
* @property int|null $is_deleted
|
||||
* @property int $brand_id
|
||||
* @property string|null $source_url
|
||||
*/
|
||||
class BrandSource extends \app\models\BaseModel
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return 'brand_source';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['created_at', 'updated_at'], 'default', 'value' => null],
|
||||
[['brand_id'], 'default', 'value' => 0],
|
||||
[['source_url'], 'default', 'value' => ''],
|
||||
[['source', 'created_at', 'updated_at', 'is_deleted', 'brand_id'], 'integer'],
|
||||
[['source_url'], 'string', 'max' => 255],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'source' => 'Source',
|
||||
'created_at' => 'Created At',
|
||||
'updated_at' => 'Updated At',
|
||||
'is_deleted' => 'Is Deleted',
|
||||
'brand_id' => 'Brand ID',
|
||||
'source_url' => 'Source Url',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
150
models/Clue.php
Normal file
150
models/Clue.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* This is the model class for table "clue".
|
||||
*
|
||||
* @property int $id 自增ID
|
||||
* @property string $clue_id 平台线索ID
|
||||
* @property string|null $req_id 请求ID
|
||||
* @property int|null $local_account_id 本地商户ID
|
||||
* @property string|null $advertiser_name 广告主名称
|
||||
* @property string|null $promotion_id 推广计划ID
|
||||
* @property string|null $promotion_name 推广计划名称
|
||||
* @property string|null $action_type 行为类型
|
||||
* @property int|null $ad_type 广告类型
|
||||
* @property string|null $video_id 视频ID
|
||||
* @property string|null $content_id 内容ID
|
||||
* @property string|null $title_id 标题ID
|
||||
* @property string|null $name 姓名
|
||||
* @property string|null $telephone 手机号
|
||||
* @property string|null $weixin 微信
|
||||
* @property string|null $gender 性别
|
||||
* @property int|null $age 年龄
|
||||
* @property string|null $province_name
|
||||
* @property string|null $city_name
|
||||
* @property string|null $county_name
|
||||
* @property string|null $address
|
||||
* @property string|null $auto_province_name 系统识别省
|
||||
* @property string|null $auto_city_name 系统识别市
|
||||
* @property string|null $country_name
|
||||
* @property string|null $author_aweme_id
|
||||
* @property string|null $author_nickname
|
||||
* @property string|null $author_role
|
||||
* @property string|null $staff_aweme_id
|
||||
* @property string|null $staff_nickname
|
||||
* @property string|null $allocation_status 分配状态
|
||||
* @property int|null $convert_status 是否转化
|
||||
* @property string|null $clue_type 线索类型
|
||||
* @property string|null $clue_return_status 线索回传状态
|
||||
* @property int|null $effective_state 是否有效
|
||||
* @property string|null $effective_state_name
|
||||
* @property string|null $follow_state_name
|
||||
* @property string|null $is_private_clue
|
||||
* @property string|null $remark 备注
|
||||
* @property string|null $remark_dict 问答/聊天记录
|
||||
* @property string|null $system_tags 系统标签
|
||||
* @property string|null $tags 自定义标签
|
||||
* @property string|null $create_time_detail 线索创建时间
|
||||
* @property string|null $modify_time 平台更新时间
|
||||
* @property int|null $created_at
|
||||
* @property int|null $updated_at
|
||||
* @property string|null $note
|
||||
* @property int|null $is_virtual
|
||||
*/
|
||||
class Clue extends \app\models\BaseModel
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return 'clue';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['req_id', 'local_account_id', 'advertiser_name', 'promotion_id', 'promotion_name', 'action_type', 'ad_type', 'video_id', 'content_id', 'title_id', 'name', 'telephone', 'weixin', 'gender', 'province_name', 'city_name', 'county_name', 'address', 'auto_province_name', 'auto_city_name', 'country_name', 'author_aweme_id', 'author_nickname', 'author_role', 'staff_aweme_id', 'staff_nickname', 'allocation_status', 'clue_type', 'clue_return_status', 'effective_state_name', 'follow_state_name', 'remark', 'remark_dict', 'system_tags', 'tags', 'create_time_detail', 'modify_time', 'note'], 'default', 'value' => null],
|
||||
[['is_virtual'], 'default', 'value' => 0],
|
||||
[['is_private_clue'], 'default', 'value' => 'NO'],
|
||||
[['clue_id'], 'required'],
|
||||
[['local_account_id', 'ad_type', 'age', 'convert_status', 'effective_state', 'created_at', 'updated_at', 'is_virtual'], 'integer'],
|
||||
[['remark', 'remark_dict', 'system_tags', 'tags', 'note'], 'string'],
|
||||
[['create_time_detail', 'modify_time'], 'safe'],
|
||||
[['clue_id', 'action_type', 'content_id', 'author_aweme_id', 'staff_aweme_id'], 'string', 'max' => 32],
|
||||
[['req_id'], 'string', 'max' => 64],
|
||||
[['advertiser_name', 'promotion_id', 'promotion_name', 'video_id', 'title_id', 'is_private_clue'], 'string', 'max' => 100],
|
||||
[['name', 'weixin', 'province_name', 'city_name', 'county_name', 'auto_province_name', 'auto_city_name', 'country_name', 'author_nickname', 'staff_nickname'], 'string', 'max' => 50],
|
||||
[['telephone', 'author_role', 'allocation_status', 'clue_type', 'clue_return_status', 'effective_state_name', 'follow_state_name'], 'string', 'max' => 20],
|
||||
[['gender'], 'string', 'max' => 16],
|
||||
[['address'], 'string', 'max' => 255],
|
||||
[['clue_id'], 'unique'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'id' => '自增ID',
|
||||
'clue_id' => '平台线索ID',
|
||||
'req_id' => '请求ID',
|
||||
'local_account_id' => '本地商户ID',
|
||||
'advertiser_name' => '广告主名称',
|
||||
'promotion_id' => '推广计划ID',
|
||||
'promotion_name' => '推广计划名称',
|
||||
'action_type' => '行为类型',
|
||||
'ad_type' => '广告类型',
|
||||
'video_id' => '视频ID',
|
||||
'content_id' => '内容ID',
|
||||
'title_id' => '标题ID',
|
||||
'name' => '姓名',
|
||||
'telephone' => '手机号',
|
||||
'weixin' => '微信',
|
||||
'gender' => '性别',
|
||||
'age' => '年龄',
|
||||
'province_name' => 'Province Name',
|
||||
'city_name' => 'City Name',
|
||||
'county_name' => 'County Name',
|
||||
'address' => 'Address',
|
||||
'auto_province_name' => '系统识别省',
|
||||
'auto_city_name' => '系统识别市',
|
||||
'country_name' => 'Country Name',
|
||||
'author_aweme_id' => 'Author Aweme ID',
|
||||
'author_nickname' => 'Author Nickname',
|
||||
'author_role' => 'Author Role',
|
||||
'staff_aweme_id' => 'Staff Aweme ID',
|
||||
'staff_nickname' => 'Staff Nickname',
|
||||
'allocation_status' => '分配状态',
|
||||
'convert_status' => '是否转化',
|
||||
'clue_type' => '线索类型',
|
||||
'clue_return_status' => '线索回传状态',
|
||||
'effective_state' => '是否有效',
|
||||
'effective_state_name' => 'Effective State Name',
|
||||
'follow_state_name' => 'Follow State Name',
|
||||
'is_private_clue' => 'Is Private Clue',
|
||||
'remark' => '备注',
|
||||
'remark_dict' => '问答/聊天记录',
|
||||
'system_tags' => '系统标签',
|
||||
'tags' => '自定义标签',
|
||||
'create_time_detail' => '线索创建时间',
|
||||
'modify_time' => '平台更新时间',
|
||||
'created_at' => 'Created At',
|
||||
'updated_at' => 'Updated At',
|
||||
'note' => 'Note',
|
||||
'is_virtual' => 'Is Virtual',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
65
models/ContactForm.php
Normal file
65
models/ContactForm.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
|
||||
/**
|
||||
* ContactForm is the model behind the contact form.
|
||||
*/
|
||||
class ContactForm extends Model
|
||||
{
|
||||
public $name;
|
||||
public $email;
|
||||
public $subject;
|
||||
public $body;
|
||||
public $verifyCode;
|
||||
|
||||
|
||||
/**
|
||||
* @return array the validation rules.
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
// name, email, subject and body are required
|
||||
[['name', 'email', 'subject', 'body'], 'required'],
|
||||
// email has to be a valid email address
|
||||
['email', 'email'],
|
||||
// verifyCode needs to be entered correctly
|
||||
['verifyCode', 'captcha'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array customized attribute labels
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'verifyCode' => 'Verification Code',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an email to the specified email address using the information collected by this model.
|
||||
* @param string $email the target email address
|
||||
* @return bool whether the model passes validation
|
||||
*/
|
||||
public function contact($email)
|
||||
{
|
||||
if ($this->validate()) {
|
||||
Yii::$app->mailer->compose()
|
||||
->setTo($email)
|
||||
->setFrom([Yii::$app->params['senderEmail'] => Yii::$app->params['senderName']])
|
||||
->setReplyTo([$this->email => $this->name])
|
||||
->setSubject($this->subject)
|
||||
->setTextBody($this->body)
|
||||
->send();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
81
models/LoginForm.php
Normal file
81
models/LoginForm.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
|
||||
/**
|
||||
* LoginForm is the model behind the login form.
|
||||
*
|
||||
* @property-read User|null $user
|
||||
*
|
||||
*/
|
||||
class LoginForm extends Model
|
||||
{
|
||||
public $username;
|
||||
public $password;
|
||||
public $rememberMe = true;
|
||||
|
||||
private $_user = false;
|
||||
|
||||
|
||||
/**
|
||||
* @return array the validation rules.
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
// username and password are both required
|
||||
[['username', 'password'], 'required'],
|
||||
// rememberMe must be a boolean value
|
||||
['rememberMe', 'boolean'],
|
||||
// password is validated by validatePassword()
|
||||
['password', 'validatePassword'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the password.
|
||||
* This method serves as the inline validation for password.
|
||||
*
|
||||
* @param string $attribute the attribute currently being validated
|
||||
* @param array $params the additional name-value pairs given in the rule
|
||||
*/
|
||||
public function validatePassword($attribute, $params)
|
||||
{
|
||||
if (!$this->hasErrors()) {
|
||||
$user = $this->getUser();
|
||||
|
||||
if (!$user || !$user->validatePassword($this->password)) {
|
||||
$this->addError($attribute, 'Incorrect username or password.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs in a user using the provided username and password.
|
||||
* @return bool whether the user is logged in successfully
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
if ($this->validate()) {
|
||||
return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user by [[username]]
|
||||
*
|
||||
* @return User|null
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
if ($this->_user === false) {
|
||||
$this->_user = User::findByUsername($this->username);
|
||||
}
|
||||
|
||||
return $this->_user;
|
||||
}
|
||||
}
|
||||
74
models/Oauth.php
Normal file
74
models/Oauth.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* This is the model class for table "oauth".
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $access_token
|
||||
* @property string $refresh_token
|
||||
* @property int $created_at
|
||||
* @property int $updated_at
|
||||
* @property string $app_id
|
||||
* @property string $auth_code
|
||||
* @property string $material_auth_status
|
||||
* @property string $scope
|
||||
* @property string $uid
|
||||
* @property int $is_init
|
||||
* @property int|null $is_delete
|
||||
* @property string|null $advertiser_ids
|
||||
*/
|
||||
class Oauth extends \app\models\BaseModel
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return 'oauth';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['advertiser_ids'], 'default', 'value' => null],
|
||||
[['uid'], 'default', 'value' => ''],
|
||||
[['is_delete'], 'default', 'value' => 0],
|
||||
[['created_at', 'updated_at', 'is_init', 'is_delete'], 'integer'],
|
||||
[['advertiser_ids'], 'string'],
|
||||
[['access_token', 'refresh_token', 'auth_code', 'material_auth_status', 'scope', 'uid'], 'string', 'max' => 255],
|
||||
[['app_id'], 'string', 'max' => 64],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'access_token' => 'Access Token',
|
||||
'refresh_token' => 'Refresh Token',
|
||||
'created_at' => 'Created At',
|
||||
'updated_at' => 'Updated At',
|
||||
'app_id' => 'App ID',
|
||||
'auth_code' => 'Auth Code',
|
||||
'material_auth_status' => 'Material Auth Status',
|
||||
'scope' => 'Scope',
|
||||
'uid' => 'Uid',
|
||||
'is_init' => 'Is Init',
|
||||
'is_delete' => 'Is Delete',
|
||||
'advertiser_ids' => 'Advertiser Ids',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
60
models/OauthAccount.php
Normal file
60
models/OauthAccount.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* This is the model class for table "oauth_account".
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $created_at
|
||||
* @property int|null $updated_at
|
||||
* @property string $admin_uid
|
||||
* @property string $account_id
|
||||
* @property string $account_name
|
||||
* @property int|null $is_delete
|
||||
*/
|
||||
class OauthAccount extends \app\models\BaseModel
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return 'oauth_account';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['is_delete'], 'default', 'value' => 0],
|
||||
[['account_name'], 'default', 'value' => ''],
|
||||
[['created_at', 'updated_at', 'is_delete'], 'integer'],
|
||||
[['admin_uid', 'account_id'], 'string', 'max' => 64],
|
||||
[['account_name'], 'string', 'max' => 255],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'created_at' => 'Created At',
|
||||
'updated_at' => 'Updated At',
|
||||
'admin_uid' => 'Admin Uid',
|
||||
'account_id' => 'Account ID',
|
||||
'account_name' => 'Account Name',
|
||||
'is_delete' => 'Is Delete',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
64
models/OauthAccountLocal.php
Normal file
64
models/OauthAccountLocal.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* This is the model class for table "oauth_account_local".
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $created_at
|
||||
* @property int|null $updated_at
|
||||
* @property string $admin_uid
|
||||
* @property string $account_id
|
||||
* @property string $advertiser_name
|
||||
* @property string $advertiser_id
|
||||
* @property int|null $is_delete
|
||||
* @property int $is_active
|
||||
*/
|
||||
class OauthAccountLocal extends \app\models\BaseModel
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return 'oauth_account_local';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['is_active'], 'default', 'value' => 0],
|
||||
[['advertiser_id'], 'default', 'value' => ''],
|
||||
[['created_at', 'updated_at', 'is_delete', 'is_active'], 'integer'],
|
||||
[['admin_uid', 'account_id', 'advertiser_id'], 'string', 'max' => 64],
|
||||
[['advertiser_name'], 'string', 'max' => 255],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'created_at' => 'Created At',
|
||||
'updated_at' => 'Updated At',
|
||||
'admin_uid' => 'Admin Uid',
|
||||
'account_id' => 'Account ID',
|
||||
'advertiser_name' => 'Advertiser Name',
|
||||
'advertiser_id' => 'Advertiser ID',
|
||||
'is_delete' => 'Is Delete',
|
||||
'is_active' => 'Is Active',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
158
models/User.php
Normal file
158
models/User.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
use yii\behaviors\TimestampBehavior;
|
||||
use yii\db\ActiveRecord;
|
||||
use yii\db\BaseActiveRecord;
|
||||
use yii\web\IdentityInterface;
|
||||
|
||||
/**
|
||||
* This is the model class for table "user".
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $username
|
||||
* @property string $auth_key
|
||||
* @property string $password_hash
|
||||
* @property string|null $password_reset_token
|
||||
* @property string $email
|
||||
* @property int $status
|
||||
* @property int $created_at
|
||||
* @property int $updated_at
|
||||
* @property string $role
|
||||
*/
|
||||
class User extends BaseModel implements IdentityInterface
|
||||
{
|
||||
|
||||
const STATUS_DELETED = 0;
|
||||
const STATUS_ACTIVE = 10;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return 'user';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['password_reset_token'], 'default', 'value' => null],
|
||||
[['status'], 'default', 'value' => 10],
|
||||
[['username', 'auth_key', 'password_hash', 'email', 'created_at', 'updated_at'], 'required'],
|
||||
[['status', 'created_at', 'updated_at'], 'integer'],
|
||||
[['username', 'password_hash', 'password_reset_token', 'email'], 'string', 'max' => 255],
|
||||
[['auth_key', 'role'], 'string', 'max' => 32],
|
||||
[['username'], 'unique'],
|
||||
[['email'], 'unique'],
|
||||
[['password_reset_token'], 'unique'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'username' => 'Username',
|
||||
'auth_key' => 'Auth Key',
|
||||
'password_hash' => 'Password Hash',
|
||||
'password_reset_token' => 'Password Reset Token',
|
||||
'email' => 'Email',
|
||||
'status' => 'Status',
|
||||
'created_at' => 'Created At',
|
||||
'updated_at' => 'Updated At',
|
||||
'role' => 'Role',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function findIdentity($id)
|
||||
{
|
||||
return static::find()
|
||||
->where(['id' => $id])
|
||||
->andWhere(['status' => self::STATUS_ACTIVE])
|
||||
->one();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function findIdentityByAccessToken($token, $type = null)
|
||||
{
|
||||
die(__FILE__);
|
||||
foreach (self::$users as $user) {
|
||||
if ($user['accessToken'] === $token) {
|
||||
return new static($user);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user by username
|
||||
*
|
||||
* @param string $username
|
||||
* @return static|null
|
||||
*/
|
||||
public static function findByUsername($username)
|
||||
{
|
||||
return static::find()
|
||||
->where(['username' => $username])
|
||||
->andWhere(['status' => self::STATUS_ACTIVE])
|
||||
->one();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthKey()
|
||||
{
|
||||
return $this->auth_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateAuthKey($authKey)
|
||||
{
|
||||
return $this->auth_key === $authKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates password
|
||||
*
|
||||
* @param string $password password to validate
|
||||
* @return bool if password provided is valid for current user
|
||||
*/
|
||||
public function validatePassword($password)
|
||||
{
|
||||
return Yii::$app->security->validatePassword(
|
||||
$password,
|
||||
$this->password_hash
|
||||
);
|
||||
}
|
||||
|
||||
public function getRole()
|
||||
{
|
||||
return $this->role;
|
||||
}
|
||||
}
|
||||
63
models/UserAdvertiser.php
Normal file
63
models/UserAdvertiser.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* This is the model class for table "user_advertiser".
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property string $advertiser_id
|
||||
* @property int|null $created_at
|
||||
* @property int|null $updated_at
|
||||
* @property int|null $is_delete
|
||||
* @property int $local_id
|
||||
* @property string $advertiser_name
|
||||
*/
|
||||
class UserAdvertiser extends \app\models\BaseModel
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return 'user_advertiser';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['is_delete'], 'default', 'value' => 0],
|
||||
[['advertiser_id'], 'default', 'value' => ''],
|
||||
[['user_id', 'created_at', 'updated_at', 'is_delete', 'local_id'], 'integer'],
|
||||
[['local_id', 'advertiser_name'], 'required'],
|
||||
[['advertiser_id'], 'string', 'max' => 255],
|
||||
[['advertiser_name'], 'string', 'max' => 64],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'user_id' => 'User ID',
|
||||
'advertiser_id' => 'Advertiser ID',
|
||||
'created_at' => 'Created At',
|
||||
'updated_at' => 'Updated At',
|
||||
'is_delete' => 'Is Delete',
|
||||
'local_id' => 'Local ID',
|
||||
'advertiser_name' => 'Advertiser Name',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
104
models/User_bak.php
Normal file
104
models/User_bak.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
class User_bak extends \yii\base\BaseObject implements \yii\web\IdentityInterface
|
||||
{
|
||||
public $id;
|
||||
public $username;
|
||||
public $password;
|
||||
public $authKey;
|
||||
public $accessToken;
|
||||
|
||||
private static $users = [
|
||||
'100' => [
|
||||
'id' => '100',
|
||||
'username' => 'admin',
|
||||
'password' => 'admin',
|
||||
'authKey' => 'test100key',
|
||||
'accessToken' => '100-token',
|
||||
],
|
||||
'101' => [
|
||||
'id' => '101',
|
||||
'username' => 'demo',
|
||||
'password' => 'demo',
|
||||
'authKey' => 'test101key',
|
||||
'accessToken' => '101-token',
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function findIdentity($id)
|
||||
{
|
||||
return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function findIdentityByAccessToken($token, $type = null)
|
||||
{
|
||||
foreach (self::$users as $user) {
|
||||
if ($user['accessToken'] === $token) {
|
||||
return new static($user);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user by username
|
||||
*
|
||||
* @param string $username
|
||||
* @return static|null
|
||||
*/
|
||||
public static function findByUsername($username)
|
||||
{
|
||||
foreach (self::$users as $user) {
|
||||
if (strcasecmp($user['username'], $username) === 0) {
|
||||
return new static($user);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthKey()
|
||||
{
|
||||
return $this->authKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateAuthKey($authKey)
|
||||
{
|
||||
return $this->authKey === $authKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates password
|
||||
*
|
||||
* @param string $password password to validate
|
||||
* @return bool if password provided is valid for current user
|
||||
*/
|
||||
public function validatePassword($password)
|
||||
{
|
||||
return $this->password === $password;
|
||||
}
|
||||
}
|
||||
113
models/Xiansuo.php
Normal file
113
models/Xiansuo.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* This is the model class for table "xiansuo".
|
||||
*
|
||||
* @property int $id 主键ID
|
||||
* @property int|null $xs_id 线索ID(业务ID)
|
||||
* @property int|null $leads_id 广告平台线索ID
|
||||
* @property string|null $name 姓名
|
||||
* @property string|null $telphone 电话
|
||||
* @property string|null $weixin 微信
|
||||
* @property string|null $gender 性别
|
||||
* @property string|null $location 所在地
|
||||
* @property string|null $tel_logic_location 电话逻辑归属地
|
||||
* @property string|null $keshi 科室
|
||||
* @property string|null $gj_value 跟进状态
|
||||
* @property string|null $beizhu 备注
|
||||
* @property string|null $adv_id 广告账户ID
|
||||
* @property string|null $adv_name 广告账户名称
|
||||
* @property int|null $promotion_id 计划ID
|
||||
* @property string|null $promotion_name 计划名
|
||||
* @property string|null $app_name 来源平台
|
||||
* @property string|null $root_adv_id 根账户ID
|
||||
* @property string|null $external_url 外部URL
|
||||
* @property string|null $clue_convert_status 转化状态
|
||||
* @property int|null $user_id 分配用户ID
|
||||
* @property string|null $user_name 分配用户名
|
||||
* @property int|null $is_daili 是否代理 0否 1是
|
||||
* @property string|null $remark_dict 问答/聊天记录
|
||||
* @property string|null $content 原始内容JSON
|
||||
* @property string|null $push_return_data 推送返回数据
|
||||
* @property string|null $date 分配时间
|
||||
* @property string|null $created_at 创建时间
|
||||
* @property string|null $updated_at 更新时间
|
||||
*/
|
||||
class Xiansuo extends \yii\db\ActiveRecord
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return 'xiansuo';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['xs_id', 'promotion_id', 'user_id', 'remark_dict', 'content', 'push_return_data', 'date', 'created_at', 'updated_at'], 'default', 'value' => null],
|
||||
[['is_daili'], 'default', 'value' => 0],
|
||||
[['user_name'], 'default', 'value' => ''],
|
||||
[['id'], 'required'],
|
||||
[['id', 'xs_id', 'leads_id', 'promotion_id', 'user_id', 'is_daili'], 'integer'],
|
||||
[['remark_dict', 'date', 'created_at', 'updated_at'], 'safe'],
|
||||
[['content', 'push_return_data'], 'string'],
|
||||
[['name', 'weixin', 'keshi', 'gj_value', 'adv_id', 'app_name', 'root_adv_id', 'clue_convert_status', 'user_name'], 'string', 'max' => 50],
|
||||
[['telphone'], 'string', 'max' => 20],
|
||||
[['gender'], 'string', 'max' => 10],
|
||||
[['location', 'tel_logic_location', 'adv_name'], 'string', 'max' => 100],
|
||||
[['beizhu', 'external_url'], 'string', 'max' => 255],
|
||||
[['promotion_name'], 'string', 'max' => 150],
|
||||
[['id'], 'unique'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'id' => '主键ID',
|
||||
'xs_id' => '线索ID(业务ID)',
|
||||
'leads_id' => '广告平台线索ID',
|
||||
'name' => '姓名',
|
||||
'telphone' => '电话',
|
||||
'weixin' => '微信',
|
||||
'gender' => '性别',
|
||||
'location' => '所在地',
|
||||
'tel_logic_location' => '电话逻辑归属地',
|
||||
'keshi' => '科室',
|
||||
'gj_value' => '跟进状态',
|
||||
'beizhu' => '备注',
|
||||
'adv_id' => '广告账户ID',
|
||||
'adv_name' => '广告账户名称',
|
||||
'promotion_id' => '计划ID',
|
||||
'promotion_name' => '计划名',
|
||||
'app_name' => '来源平台',
|
||||
'root_adv_id' => '根账户ID',
|
||||
'external_url' => '外部URL',
|
||||
'clue_convert_status' => '转化状态',
|
||||
'user_id' => '分配用户ID',
|
||||
'user_name' => '分配用户名',
|
||||
'is_daili' => '是否代理 0否 1是',
|
||||
'remark_dict' => '问答/聊天记录',
|
||||
'content' => '原始内容JSON',
|
||||
'push_return_data' => '推送返回数据',
|
||||
'date' => '分配时间',
|
||||
'created_at' => '创建时间',
|
||||
'updated_at' => '更新时间',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
93
models/jobs/RunwayJob.php
Normal file
93
models/jobs/RunwayJob.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace app\models\jobs;
|
||||
|
||||
use app\common\CommonHelper;
|
||||
use app\enums\SourceEnum;
|
||||
use app\models\BrandRunway;
|
||||
use app\models\BrandRunwayImages;
|
||||
use app\models\BrandSource;
|
||||
use app\models\jobs\base\BaseJob;
|
||||
use app\models\logics\commands\SpiderVogue;
|
||||
use yii\debug\models\search\Db;
|
||||
use yii\queue\Queue;
|
||||
|
||||
class RunwayJob extends BaseJob
|
||||
{
|
||||
public BrandSource|null $source = null;
|
||||
|
||||
public function execute($queue)
|
||||
{
|
||||
$spiderModel = new SpiderVogue();
|
||||
$runwayList = ($spiderModel->getBrandRunwayList(rtrim(SourceEnum::VOGEU->baseUrl(), '/') . $this->source->source_url));
|
||||
echo '获取到了 --> ' . count($runwayList ?: []) . '篇文章' . PHP_EOL;
|
||||
try {
|
||||
foreach ($runwayList as $runwayItem) {
|
||||
$tr = \Yii::$app->db->beginTransaction();
|
||||
echo "正在处理{$runwayItem['hed']}" . PHP_EOL;
|
||||
if (!$model = BrandRunway::find()->where(['brand_id' => $this->source->brand_id, 'title' => $runwayItem['hed']])->one()) {
|
||||
$model = new BrandRunway();
|
||||
$model->title = $runwayItem['hed'];
|
||||
$model->brand_id = $this->source->brand_id;
|
||||
$model->year = CommonHelper::getYear($runwayItem['hed']);
|
||||
$model->cover = '';
|
||||
$model->save();
|
||||
var_dump($model->errors);
|
||||
if (!$sourceModel = BrandSource::find()->where(['brand_id' => $this->source->brand_id, 'is_deleted' => 0])->one()) {
|
||||
$sourceModel = new BrandSource();
|
||||
$sourceModel->brand_id = $this->source->brand_id;
|
||||
$sourceModel->source = SourceEnum::VOGEU->value();
|
||||
$sourceModel->save();
|
||||
var_dump($sourceModel->errors);
|
||||
}
|
||||
|
||||
$pageUri = $runwayItem['url'];
|
||||
$requestUrl = rtrim(SourceEnum::VOGEU->baseUrl(), '/') . $pageUri . '/slideshow/collection';
|
||||
$detailData = $spiderModel->getDetail($requestUrl);
|
||||
|
||||
preg_match_all('/window\.__PRELOADED_STATE__ = (.*?);</s', $detailData, $matches);
|
||||
|
||||
$saveUrl = $detailUrl = [];
|
||||
if (count($matches) > 1) {
|
||||
$val = json_decode(current($matches[1]), true);
|
||||
$images = $val['transformed']['runwayGalleries']['galleries'][0]['items'] ?? false;
|
||||
|
||||
if ($images === false) {
|
||||
echo '获取图片失败' . PHP_EOL;
|
||||
// $this->logger->warning($requestUrl . '获取图片失败.');
|
||||
$tr->rollBack();
|
||||
continue;
|
||||
// return;
|
||||
}
|
||||
echo "文章图片数量" . count($images) . PHP_EOL;
|
||||
foreach (is_array($images) ? $images : [] as $img) {
|
||||
$saveUrl[] = [
|
||||
'src' => $img['image']['sources']['xxl']['url']
|
||||
];
|
||||
// foreach ($img['details'] ?? [] as $detail) {
|
||||
// $detailUrl[] = ['src' => $detail['image']['sources']['xxl']['url']];
|
||||
$brandModel = new BrandRunwayImages();
|
||||
$brandModel->image = $img['image']['sources']['xxl']['url'];
|
||||
$brandModel->runway_id = $model->id;
|
||||
$brandModel->brand_id = $this->source->brand_id;
|
||||
$brandModel->save();
|
||||
var_dump($brandModel->errors);
|
||||
// }
|
||||
}
|
||||
|
||||
// $model->images = json_encode($saveUrl);
|
||||
$model->image_count = count($saveUrl);
|
||||
$model->cover = current($saveUrl)['src'];
|
||||
$model->source_url = $requestUrl;
|
||||
$model->save();
|
||||
}
|
||||
}
|
||||
|
||||
$tr->commit();
|
||||
}
|
||||
}catch (\Throwable $exception) {
|
||||
var_dump($exception->getMessage());
|
||||
}
|
||||
// TODO: Implement execute() method.
|
||||
}
|
||||
}
|
||||
14
models/jobs/base/BaseJob.php
Normal file
14
models/jobs/base/BaseJob.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace app\models\jobs\base;
|
||||
|
||||
use yii\queue\Queue;
|
||||
|
||||
class BaseJob extends \yii\base\BaseObject implements \yii\queue\JobInterface
|
||||
{
|
||||
|
||||
public function execute($queue)
|
||||
{
|
||||
// TODO: Implement execute() method.
|
||||
}
|
||||
}
|
||||
38
models/logics/commands/Base/BaseCommandSpiderLogic.php
Normal file
38
models/logics/commands/Base/BaseCommandSpiderLogic.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace app\models\logics\commands\base;
|
||||
|
||||
use app\common\CurlApp;
|
||||
|
||||
class BaseCommandSpiderLogic
|
||||
{
|
||||
|
||||
protected ?CurlApp $curl = null;
|
||||
|
||||
protected string $brandName = '';
|
||||
|
||||
protected string $baseUrl = '';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
ini_set('pcre.backtrack_limit', '-1');
|
||||
$this->curl = new CurlApp();
|
||||
}
|
||||
|
||||
public function exec()
|
||||
{
|
||||
|
||||
$url = rtrim($this->baseUrl, '/') . '/fashion-shows/designer/' . $this->brandName;
|
||||
// var_dump($url);
|
||||
// list($request, $httpCode) = $curl->setUrl($url)->exec();
|
||||
$request = $this->curl->setUrl($url)->exec();
|
||||
|
||||
if ($request) {
|
||||
preg_match_all('/window.__PRELOADED_STATE__ = ([\s\S]*?);<\/script>/', $request, $matches);
|
||||
$val = json_decode(current(end($matches)), true);
|
||||
return $val['transformed']['runwayDesignerContent']['designerCollections'] ?? [];
|
||||
} else {
|
||||
// $this->logger->info('未找到数据.');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
190
models/logics/commands/SpiderVogue.php
Normal file
190
models/logics/commands/SpiderVogue.php
Normal file
@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace app\models\logics\commands;
|
||||
|
||||
use app\common\CommonHelper;
|
||||
use app\enums\SourceEnum;
|
||||
use app\models\Brand;
|
||||
use app\models\BrandRunway;
|
||||
use app\models\BrandRunwayImages;
|
||||
use app\models\BrandSource;
|
||||
use app\models\logics\commands\base\BaseCommandSpiderLogic;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
class SpiderVogue extends BaseCommandSpiderLogic
|
||||
{
|
||||
protected string $baseUrl = 'https://www.vogue.com/';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
// $this->curl->setProxy('127.0.0.1', 58591);
|
||||
}
|
||||
|
||||
public function setBrandName(string $name = ''): SpiderVogue
|
||||
{
|
||||
$this->brandName = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
$source = BrandSource::find()->where(['source' => SourceEnum::VOGEU->value()])->asArray()->all();
|
||||
// var_dump(ArrayHelper::getColumn($source, 'brand_id'));
|
||||
$i = 0;
|
||||
$brandNameContainer = [];
|
||||
$brandIdContainer = [];
|
||||
foreach (Brand::find()->where(
|
||||
['>','id', 5747] // 5897
|
||||
)->each() as $item) {
|
||||
$item['name'] = strtr($item['name'], [
|
||||
' ' => '-',
|
||||
'.' => '-'
|
||||
]);
|
||||
// var_dump(strtolower($item['name']));
|
||||
// $runwayList = $this->setBrandName($item['name'])->_getBrandRunwayList(strtolower($item['name']));
|
||||
echo $item['name'] . PHP_EOL;
|
||||
|
||||
$brandNameContainer[] = strtolower($item['name']);
|
||||
$brandIdContainer[] = $item['id'];
|
||||
$i++;
|
||||
|
||||
if ($i != 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
$runwayList = $this->_getBrandRunwayListMulti($brandNameContainer);
|
||||
|
||||
foreach ($runwayList as $index => $listItem) {
|
||||
// var_dump('id---', $brandIdContainer[$index]);
|
||||
// var_dump($listItem);
|
||||
if (!$listItem) {
|
||||
continue;
|
||||
}
|
||||
if (!$sourceModel = BrandSource::find()->where(['brand_id' => $brandIdContainer[$index], 'is_deleted' => 0])->one()) {
|
||||
$sourceModel = new BrandSource();
|
||||
$sourceModel->brand_id = $brandIdContainer[$index];
|
||||
$sourceModel->source = SourceEnum::VOGEU->value();
|
||||
$sourceModel->save();
|
||||
var_dump($sourceModel->errors);
|
||||
}
|
||||
}
|
||||
|
||||
$brandNameContainer = $brandIdContainer = [];
|
||||
$i = 0;
|
||||
|
||||
if (!$runwayList) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var_dump($runwayList);
|
||||
foreach ($runwayList as $runwayItem) {
|
||||
var_dump($runwayItem['hed']);
|
||||
if (!$model = BrandRunway::find()->where(['brand_id' => $item['id'], 'title' => $runwayItem['hed']])->one()) {
|
||||
echo $runwayItem['hed'] . PHP_EOL;
|
||||
$model = new BrandRunway();
|
||||
$model->title = $runwayItem['hed'];
|
||||
$model->brand_id = $item['id'];
|
||||
$model->year = CommonHelper::getYear($runwayItem['hed']);
|
||||
$model->cover = '';
|
||||
$model->save();
|
||||
var_dump($model->errors);
|
||||
if (!$sourceModel = BrandSource::find()->where(['brand_id' => $item['id'], 'is_deleted' => 0])->one()) {
|
||||
$sourceModel = new BrandSource();
|
||||
$sourceModel->brand_id = $item['id'];
|
||||
$sourceModel->source = SourceEnum::VOGEU->value();
|
||||
$sourceModel->save();
|
||||
var_dump($sourceModel->errors);
|
||||
}
|
||||
|
||||
$pageUri = $runwayItem['url'];
|
||||
$requestUrl = rtrim($this->baseUrl, '/') . $pageUri . '/slideshow/collection';
|
||||
$detailData = $this->_getDetail($requestUrl);
|
||||
|
||||
preg_match_all('/window\.__PRELOADED_STATE__ = (.*?);</s', $detailData, $matches);
|
||||
|
||||
$saveUrl = $detailUrl = [];
|
||||
if (count($matches) > 1) {
|
||||
$val = json_decode(current($matches[1]), true);
|
||||
$images = $val['transformed']['runwayGalleries']['galleries'][0]['items'] ?? false;
|
||||
|
||||
if ($images === false) {
|
||||
echo '获取图片失败' . PHP_EOL;
|
||||
// $this->logger->warning($requestUrl . '获取图片失败.');
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (is_array($images) ? $images : [] as $img) {
|
||||
$saveUrl[] = [
|
||||
'src' => $img['image']['sources']['xxl']['url']
|
||||
];
|
||||
foreach ($img['details'] ?? [] as $detail) {
|
||||
$detailUrl[] = ['src' => $detail['image']['sources']['xxl']['url']];
|
||||
$brandModel = new BrandRunwayImages();
|
||||
$brandModel->image = $detail['image']['sources']['xxl']['url'];
|
||||
$brandModel->runway_id = $model->id;
|
||||
$brandModel->brand_id = $item['id'];
|
||||
$brandModel->save();
|
||||
var_dump($brandModel->errors);
|
||||
}
|
||||
}
|
||||
|
||||
// $model->images = json_encode($saveUrl);
|
||||
$model->cover = '';
|
||||
$model->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getDetail($url): bool|string
|
||||
{
|
||||
return $this->curl->setUrl($url)->exec();
|
||||
}
|
||||
|
||||
|
||||
public function getBrandRunwayList(string $url)
|
||||
{
|
||||
$request = $this->curl->setUrl($url)->exec();
|
||||
|
||||
if ($request) {
|
||||
preg_match_all('/window.__PRELOADED_STATE__ = ([\s\S]*?);<\/script>/', $request, $matches);
|
||||
$val = json_decode(current(end($matches)), true);
|
||||
return $val['transformed']['runwayDesignerContent']['designerCollections'] ?? [];
|
||||
} else {
|
||||
// $this->logger->info('未找到数据.');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private function _getBrandRunwayListMulti(array $brandNames = []): array
|
||||
{
|
||||
// $brandNames = [];
|
||||
$resp = [];
|
||||
foreach ($brandNames as $brandName) {
|
||||
$url[] = rtrim($this->baseUrl, '/') . '/fashion-shows/designer/' . $brandName;
|
||||
// var_dump($url);
|
||||
// list($request, $httpCode) = $curl->setUrl($url)->exec();
|
||||
|
||||
}
|
||||
|
||||
$request = $this->curl->execMulti($url);
|
||||
// var_dump($request);die;
|
||||
foreach ($request as $item) {
|
||||
if ($item) {
|
||||
preg_match_all('/window.__PRELOADED_STATE__ = ([\s\S]*?);<\/script>/', $item, $matches);
|
||||
$val = json_decode(current(end($matches)), true);
|
||||
$resp[] = $val['transformed']['runwayDesignerContent']['designerCollections'] ?? [];
|
||||
} else {
|
||||
// $this->logger->info('未找到数据.');
|
||||
$resp[] = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $resp;
|
||||
}
|
||||
|
||||
}
|
||||
162
requirements.php
Normal file
162
requirements.php
Normal file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/**
|
||||
* Application requirement checker script.
|
||||
*
|
||||
* In order to run this script use the following console command:
|
||||
* php requirements.php
|
||||
*
|
||||
* In order to run this script from the web, you should copy it to the web root.
|
||||
* If you are using Linux you can create a hard link instead, using the following command:
|
||||
* ln ../requirements.php requirements.php
|
||||
*/
|
||||
|
||||
// you may need to adjust this path to the correct Yii framework path
|
||||
// uncomment and adjust the following line if Yii is not located at the default path
|
||||
//$frameworkPath = dirname(__FILE__) . '/vendor/yiisoft/yii2';
|
||||
|
||||
|
||||
if (!isset($frameworkPath)) {
|
||||
$searchPaths = array(
|
||||
dirname(__FILE__) . '/vendor/yiisoft/yii2',
|
||||
dirname(__FILE__) . '/../vendor/yiisoft/yii2',
|
||||
);
|
||||
foreach ($searchPaths as $path) {
|
||||
if (is_dir($path)) {
|
||||
$frameworkPath = $path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($frameworkPath) || !is_dir($frameworkPath)) {
|
||||
$message = "<h1>Error</h1>\n\n"
|
||||
. "<p><strong>The path to yii framework seems to be incorrect.</strong></p>\n"
|
||||
. '<p>You need to install Yii framework via composer or adjust the framework path in file <abbr title="' . __FILE__ . '">' . basename(__FILE__) . "</abbr>.</p>\n"
|
||||
. '<p>Please refer to the <abbr title="' . dirname(__FILE__) . "/README.md\">README</abbr> on how to install Yii.</p>\n";
|
||||
|
||||
if (!empty($_SERVER['argv'])) {
|
||||
// do not print HTML when used in console mode
|
||||
echo strip_tags($message);
|
||||
} else {
|
||||
echo $message;
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once($frameworkPath . '/requirements/YiiRequirementChecker.php');
|
||||
$requirementsChecker = new YiiRequirementChecker();
|
||||
|
||||
$gdMemo = $imagickMemo = 'Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required for image CAPTCHA.';
|
||||
$gdOK = $imagickOK = false;
|
||||
|
||||
if (extension_loaded('imagick')) {
|
||||
$imagick = new Imagick();
|
||||
$imagickFormats = $imagick->queryFormats('PNG');
|
||||
if (in_array('PNG', $imagickFormats)) {
|
||||
$imagickOK = true;
|
||||
} else {
|
||||
$imagickMemo = 'Imagick extension should be installed with PNG support in order to be used for image CAPTCHA.';
|
||||
}
|
||||
}
|
||||
|
||||
if (extension_loaded('gd')) {
|
||||
$gdInfo = gd_info();
|
||||
if (!empty($gdInfo['FreeType Support'])) {
|
||||
$gdOK = true;
|
||||
} else {
|
||||
$gdMemo = 'GD extension should be installed with FreeType support in order to be used for image CAPTCHA.';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust requirements according to your application specifics.
|
||||
*/
|
||||
$requirements = array(
|
||||
// Database :
|
||||
array(
|
||||
'name' => 'PDO extension',
|
||||
'mandatory' => true,
|
||||
'condition' => extension_loaded('pdo'),
|
||||
'by' => 'All DB-related classes',
|
||||
),
|
||||
array(
|
||||
'name' => 'PDO SQLite extension',
|
||||
'mandatory' => false,
|
||||
'condition' => extension_loaded('pdo_sqlite'),
|
||||
'by' => 'All DB-related classes',
|
||||
'memo' => 'Required for SQLite database.',
|
||||
),
|
||||
array(
|
||||
'name' => 'PDO MySQL extension',
|
||||
'mandatory' => false,
|
||||
'condition' => extension_loaded('pdo_mysql'),
|
||||
'by' => 'All DB-related classes',
|
||||
'memo' => 'Required for MySQL database.',
|
||||
),
|
||||
array(
|
||||
'name' => 'PDO PostgreSQL extension',
|
||||
'mandatory' => false,
|
||||
'condition' => extension_loaded('pdo_pgsql'),
|
||||
'by' => 'All DB-related classes',
|
||||
'memo' => 'Required for PostgreSQL database.',
|
||||
),
|
||||
// Cache :
|
||||
array(
|
||||
'name' => 'Memcache extension',
|
||||
'mandatory' => false,
|
||||
'condition' => extension_loaded('memcache') || extension_loaded('memcached'),
|
||||
'by' => '<a href="https://www.yiiframework.com/doc-2.0/yii-caching-memcache.html">MemCache</a>',
|
||||
'memo' => extension_loaded('memcached') ? 'To use memcached set <a href="https://www.yiiframework.com/doc-2.0/yii-caching-memcache.html#$useMemcached-detail">MemCache::useMemcached</a> to <code>true</code>.' : ''
|
||||
),
|
||||
// CAPTCHA:
|
||||
array(
|
||||
'name' => 'GD PHP extension with FreeType support',
|
||||
'mandatory' => false,
|
||||
'condition' => $gdOK,
|
||||
'by' => '<a href="https://www.yiiframework.com/doc-2.0/yii-captcha-captcha.html">Captcha</a>',
|
||||
'memo' => $gdMemo,
|
||||
),
|
||||
array(
|
||||
'name' => 'ImageMagick PHP extension with PNG support',
|
||||
'mandatory' => false,
|
||||
'condition' => $imagickOK,
|
||||
'by' => '<a href="https://www.yiiframework.com/doc-2.0/yii-captcha-captcha.html">Captcha</a>',
|
||||
'memo' => $imagickMemo,
|
||||
),
|
||||
// PHP ini :
|
||||
'phpExposePhp' => array(
|
||||
'name' => 'Expose PHP',
|
||||
'mandatory' => false,
|
||||
'condition' => $requirementsChecker->checkPhpIniOff("expose_php"),
|
||||
'by' => 'Security reasons',
|
||||
'memo' => '"expose_php" should be disabled at php.ini',
|
||||
),
|
||||
'phpAllowUrlInclude' => array(
|
||||
'name' => 'PHP allow url include',
|
||||
'mandatory' => false,
|
||||
'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"),
|
||||
'by' => 'Security reasons',
|
||||
'memo' => '"allow_url_include" should be disabled at php.ini',
|
||||
),
|
||||
'phpSmtp' => array(
|
||||
'name' => 'PHP mail SMTP',
|
||||
'mandatory' => false,
|
||||
'condition' => strlen(ini_get('SMTP')) > 0,
|
||||
'by' => 'Email sending',
|
||||
'memo' => 'PHP mail SMTP server required',
|
||||
),
|
||||
);
|
||||
|
||||
// OPcache check
|
||||
if (!version_compare(phpversion(), '5.5', '>=')) {
|
||||
$requirements[] = array(
|
||||
'name' => 'APC extension',
|
||||
'mandatory' => false,
|
||||
'condition' => extension_loaded('apc'),
|
||||
'by' => '<a href="https://www.yiiframework.com/doc-2.0/yii-caching-apccache.html">ApcCache</a>',
|
||||
);
|
||||
}
|
||||
|
||||
$result = $requirementsChecker->checkYii()->check($requirements)->getResult();
|
||||
$requirementsChecker->render();
|
||||
exit($result['summary']['errors'] === 0 ? 0 : 1);
|
||||
2
runtime/.gitignore
vendored
Normal file
2
runtime/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
BIN
tests/.DS_Store
vendored
Normal file
BIN
tests/.DS_Store
vendored
Normal file
Binary file not shown.
6
tests/_bootstrap.php
Normal file
6
tests/_bootstrap.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
define('YII_ENV', 'test');
|
||||
defined('YII_DEBUG') or define('YII_DEBUG', true);
|
||||
|
||||
require_once __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';
|
||||
require __DIR__ .'/../vendor/autoload.php';
|
||||
1
tests/_data/.gitkeep
Normal file
1
tests/_data/.gitkeep
Normal file
@ -0,0 +1 @@
|
||||
|
||||
2
tests/_output/.gitignore
vendored
Normal file
2
tests/_output/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
26
tests/_support/AcceptanceTester.php
Normal file
26
tests/_support/AcceptanceTester.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Inherited Methods
|
||||
* @method void wantToTest($text)
|
||||
* @method void wantTo($text)
|
||||
* @method void execute($callable)
|
||||
* @method void expectTo($prediction)
|
||||
* @method void expect($prediction)
|
||||
* @method void amGoingTo($argumentation)
|
||||
* @method void am($role)
|
||||
* @method void lookForwardTo($achieveValue)
|
||||
* @method void comment($description)
|
||||
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
|
||||
*
|
||||
* @SuppressWarnings(PHPMD)
|
||||
*/
|
||||
class AcceptanceTester extends \Codeception\Actor
|
||||
{
|
||||
use _generated\AcceptanceTesterActions;
|
||||
|
||||
/**
|
||||
* Define custom actions here
|
||||
*/
|
||||
}
|
||||
23
tests/_support/FunctionalTester.php
Normal file
23
tests/_support/FunctionalTester.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Inherited Methods
|
||||
* @method void wantToTest($text)
|
||||
* @method void wantTo($text)
|
||||
* @method void execute($callable)
|
||||
* @method void expectTo($prediction)
|
||||
* @method void expect($prediction)
|
||||
* @method void amGoingTo($argumentation)
|
||||
* @method void am($role)
|
||||
* @method void lookForwardTo($achieveValue)
|
||||
* @method void comment($description)
|
||||
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
|
||||
*
|
||||
* @SuppressWarnings(PHPMD)
|
||||
*/
|
||||
class FunctionalTester extends \Codeception\Actor
|
||||
{
|
||||
use _generated\FunctionalTesterActions;
|
||||
|
||||
}
|
||||
26
tests/_support/UnitTester.php
Normal file
26
tests/_support/UnitTester.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Inherited Methods
|
||||
* @method void wantToTest($text)
|
||||
* @method void wantTo($text)
|
||||
* @method void execute($callable)
|
||||
* @method void expectTo($prediction)
|
||||
* @method void expect($prediction)
|
||||
* @method void amGoingTo($argumentation)
|
||||
* @method void am($role)
|
||||
* @method void lookForwardTo($achieveValue)
|
||||
* @method void comment($description)
|
||||
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
|
||||
*
|
||||
* @SuppressWarnings(PHPMD)
|
||||
*/
|
||||
class UnitTester extends \Codeception\Actor
|
||||
{
|
||||
use _generated\UnitTesterActions;
|
||||
|
||||
/**
|
||||
* Define custom actions here
|
||||
*/
|
||||
}
|
||||
10
tests/acceptance.suite.yml.example
Normal file
10
tests/acceptance.suite.yml.example
Normal file
@ -0,0 +1,10 @@
|
||||
actor: AcceptanceTester
|
||||
modules:
|
||||
enabled:
|
||||
- WebDriver:
|
||||
url: http://127.0.0.1:8080/
|
||||
browser: firefox
|
||||
- Yii2:
|
||||
part: orm
|
||||
entryScript: index-test.php
|
||||
cleanup: false
|
||||
12
tests/acceptance/AboutCest.php
Normal file
12
tests/acceptance/AboutCest.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use yii\helpers\Url;
|
||||
|
||||
class AboutCest
|
||||
{
|
||||
public function ensureThatAboutWorks(AcceptanceTester $I)
|
||||
{
|
||||
$I->amOnPage(Url::toRoute('/site/about'));
|
||||
$I->see('About', 'h1');
|
||||
}
|
||||
}
|
||||
34
tests/acceptance/ContactCest.php
Normal file
34
tests/acceptance/ContactCest.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use yii\helpers\Url;
|
||||
|
||||
class ContactCest
|
||||
{
|
||||
public function _before(\AcceptanceTester $I)
|
||||
{
|
||||
$I->amOnPage(Url::toRoute('/site/contact'));
|
||||
}
|
||||
|
||||
public function contactPageWorks(AcceptanceTester $I)
|
||||
{
|
||||
$I->wantTo('ensure that contact page works');
|
||||
$I->see('Contact', 'h1');
|
||||
}
|
||||
|
||||
public function contactFormCanBeSubmitted(AcceptanceTester $I)
|
||||
{
|
||||
$I->amGoingTo('submit contact form with correct data');
|
||||
$I->fillField('#contactform-name', 'tester');
|
||||
$I->fillField('#contactform-email', 'tester@example.com');
|
||||
$I->fillField('#contactform-subject', 'test subject');
|
||||
$I->fillField('#contactform-body', 'test content');
|
||||
$I->fillField('#contactform-verifycode', 'testme');
|
||||
|
||||
$I->click('contact-button');
|
||||
|
||||
$I->wait(2); // wait for button to be clicked
|
||||
|
||||
$I->dontSeeElement('#contact-form');
|
||||
$I->see('Thank you for contacting us. We will respond to you as soon as possible.');
|
||||
}
|
||||
}
|
||||
18
tests/acceptance/HomeCest.php
Normal file
18
tests/acceptance/HomeCest.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
use yii\helpers\Url;
|
||||
|
||||
class HomeCest
|
||||
{
|
||||
public function ensureThatHomePageWorks(AcceptanceTester $I)
|
||||
{
|
||||
$I->amOnPage(Url::toRoute('/site/index'));
|
||||
$I->see('My Company');
|
||||
|
||||
$I->seeLink('About');
|
||||
$I->click('About');
|
||||
$I->wait(2); // wait for page to be opened
|
||||
|
||||
$I->see('This is the About page.');
|
||||
}
|
||||
}
|
||||
21
tests/acceptance/LoginCest.php
Normal file
21
tests/acceptance/LoginCest.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
use yii\helpers\Url;
|
||||
|
||||
class LoginCest
|
||||
{
|
||||
public function ensureThatLoginWorks(AcceptanceTester $I)
|
||||
{
|
||||
$I->amOnPage(Url::toRoute('/site/login'));
|
||||
$I->see('Login', 'h1');
|
||||
|
||||
$I->amGoingTo('try to login with correct credentials');
|
||||
$I->fillField('input[name="LoginForm[username]"]', 'admin');
|
||||
$I->fillField('input[name="LoginForm[password]"]', 'admin');
|
||||
$I->click('login-button');
|
||||
$I->wait(2); // wait for button to be clicked
|
||||
|
||||
$I->expectTo('see user info');
|
||||
$I->see('Logout');
|
||||
}
|
||||
}
|
||||
1
tests/acceptance/_bootstrap.php
Normal file
1
tests/acceptance/_bootstrap.php
Normal file
@ -0,0 +1 @@
|
||||
<?php
|
||||
29
tests/bin/yii
Executable file
29
tests/bin/yii
Executable file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Yii console bootstrap file.
|
||||
*
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
defined('YII_DEBUG') or define('YII_DEBUG', true);
|
||||
defined('YII_ENV') or define('YII_ENV', 'test');
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php';
|
||||
|
||||
$config = yii\helpers\ArrayHelper::merge(
|
||||
require __DIR__ . '/../../config/console.php',
|
||||
[
|
||||
'components' => [
|
||||
'db' => require __DIR__ . '/../../config/test_db.php'
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
$application = new yii\console\Application($config);
|
||||
$exitCode = $application->run();
|
||||
exit($exitCode);
|
||||
20
tests/bin/yii.bat
Normal file
20
tests/bin/yii.bat
Normal file
@ -0,0 +1,20 @@
|
||||
@echo off
|
||||
|
||||
rem -------------------------------------------------------------
|
||||
rem Yii command line bootstrap script for Windows.
|
||||
rem
|
||||
rem @author Qiang Xue <qiang.xue@gmail.com>
|
||||
rem @link https://www.yiiframework.com/
|
||||
rem @copyright Copyright (c) 2008 Yii Software LLC
|
||||
rem @license https://www.yiiframework.com/license/
|
||||
rem -------------------------------------------------------------
|
||||
|
||||
@setlocal
|
||||
|
||||
set YII_PATH=%~dp0
|
||||
|
||||
if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
|
||||
|
||||
"%PHP_COMMAND%" "%YII_PATH%yii" %*
|
||||
|
||||
@endlocal
|
||||
14
tests/functional.suite.yml
Normal file
14
tests/functional.suite.yml
Normal file
@ -0,0 +1,14 @@
|
||||
# Codeception Test Suite Configuration
|
||||
|
||||
# suite for functional (integration) tests.
|
||||
# emulate web requests and make application process them.
|
||||
# (tip: better to use with frameworks).
|
||||
|
||||
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
|
||||
#basic/web/index.php
|
||||
actor: FunctionalTester
|
||||
modules:
|
||||
enabled:
|
||||
- Filesystem
|
||||
- Yii2
|
||||
- Asserts
|
||||
57
tests/functional/ContactFormCest.php
Normal file
57
tests/functional/ContactFormCest.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
class ContactFormCest
|
||||
{
|
||||
public function _before(\FunctionalTester $I)
|
||||
{
|
||||
$I->amOnRoute('site/contact');
|
||||
}
|
||||
|
||||
public function openContactPage(\FunctionalTester $I)
|
||||
{
|
||||
$I->see('Contact', 'h1');
|
||||
}
|
||||
|
||||
public function submitEmptyForm(\FunctionalTester $I)
|
||||
{
|
||||
$I->submitForm('#contact-form', []);
|
||||
$I->expectTo('see validations errors');
|
||||
$I->see('Contact', 'h1');
|
||||
$I->see('Name cannot be blank');
|
||||
$I->see('Email cannot be blank');
|
||||
$I->see('Subject cannot be blank');
|
||||
$I->see('Body cannot be blank');
|
||||
$I->see('The verification code is incorrect');
|
||||
}
|
||||
|
||||
public function submitFormWithIncorrectEmail(\FunctionalTester $I)
|
||||
{
|
||||
$I->submitForm('#contact-form', [
|
||||
'ContactForm[name]' => 'tester',
|
||||
'ContactForm[email]' => 'tester.email',
|
||||
'ContactForm[subject]' => 'test subject',
|
||||
'ContactForm[body]' => 'test content',
|
||||
'ContactForm[verifyCode]' => 'testme',
|
||||
]);
|
||||
$I->expectTo('see that email address is wrong');
|
||||
$I->dontSee('Name cannot be blank', '.help-inline');
|
||||
$I->see('Email is not a valid email address.');
|
||||
$I->dontSee('Subject cannot be blank', '.help-inline');
|
||||
$I->dontSee('Body cannot be blank', '.help-inline');
|
||||
$I->dontSee('The verification code is incorrect', '.help-inline');
|
||||
}
|
||||
|
||||
public function submitFormSuccessfully(\FunctionalTester $I)
|
||||
{
|
||||
$I->submitForm('#contact-form', [
|
||||
'ContactForm[name]' => 'tester',
|
||||
'ContactForm[email]' => 'tester@example.com',
|
||||
'ContactForm[subject]' => 'test subject',
|
||||
'ContactForm[body]' => 'test content',
|
||||
'ContactForm[verifyCode]' => 'testme',
|
||||
]);
|
||||
$I->seeEmailIsSent();
|
||||
$I->dontSeeElement('#contact-form');
|
||||
$I->see('Thank you for contacting us. We will respond to you as soon as possible.');
|
||||
}
|
||||
}
|
||||
59
tests/functional/LoginFormCest.php
Normal file
59
tests/functional/LoginFormCest.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
class LoginFormCest
|
||||
{
|
||||
public function _before(\FunctionalTester $I)
|
||||
{
|
||||
$I->amOnRoute('site/login');
|
||||
}
|
||||
|
||||
public function openLoginPage(\FunctionalTester $I)
|
||||
{
|
||||
$I->see('Login', 'h1');
|
||||
|
||||
}
|
||||
|
||||
// demonstrates `amLoggedInAs` method
|
||||
public function internalLoginById(\FunctionalTester $I)
|
||||
{
|
||||
$I->amLoggedInAs(100);
|
||||
$I->amOnPage('/');
|
||||
$I->see('Logout (admin)');
|
||||
}
|
||||
|
||||
// demonstrates `amLoggedInAs` method
|
||||
public function internalLoginByInstance(\FunctionalTester $I)
|
||||
{
|
||||
$I->amLoggedInAs(\app\models\User::findByUsername('admin'));
|
||||
$I->amOnPage('/');
|
||||
$I->see('Logout (admin)');
|
||||
}
|
||||
|
||||
public function loginWithEmptyCredentials(\FunctionalTester $I)
|
||||
{
|
||||
$I->submitForm('#login-form', []);
|
||||
$I->expectTo('see validations errors');
|
||||
$I->see('Username cannot be blank.');
|
||||
$I->see('Password cannot be blank.');
|
||||
}
|
||||
|
||||
public function loginWithWrongCredentials(\FunctionalTester $I)
|
||||
{
|
||||
$I->submitForm('#login-form', [
|
||||
'LoginForm[username]' => 'admin',
|
||||
'LoginForm[password]' => 'wrong',
|
||||
]);
|
||||
$I->expectTo('see validations errors');
|
||||
$I->see('Incorrect username or password.');
|
||||
}
|
||||
|
||||
public function loginSuccessfully(\FunctionalTester $I)
|
||||
{
|
||||
$I->submitForm('#login-form', [
|
||||
'LoginForm[username]' => 'admin',
|
||||
'LoginForm[password]' => 'admin',
|
||||
]);
|
||||
$I->see('Logout (admin)');
|
||||
$I->dontSeeElement('form#login-form');
|
||||
}
|
||||
}
|
||||
1
tests/functional/_bootstrap.php
Normal file
1
tests/functional/_bootstrap.php
Normal file
@ -0,0 +1 @@
|
||||
<?php
|
||||
11
tests/unit.suite.yml
Normal file
11
tests/unit.suite.yml
Normal file
@ -0,0 +1,11 @@
|
||||
# Codeception Test Suite Configuration
|
||||
|
||||
# suite for unit (internal) tests.
|
||||
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
|
||||
|
||||
actor: UnitTester
|
||||
modules:
|
||||
enabled:
|
||||
- Asserts
|
||||
- Yii2:
|
||||
part: [orm, email, fixtures]
|
||||
3
tests/unit/_bootstrap.php
Normal file
3
tests/unit/_bootstrap.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
// add unit testing specific bootstrap code here
|
||||
41
tests/unit/models/ContactFormTest.php
Normal file
41
tests/unit/models/ContactFormTest.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace tests\unit\models;
|
||||
|
||||
use app\models\ContactForm;
|
||||
use yii\mail\MessageInterface;
|
||||
|
||||
class ContactFormTest extends \Codeception\Test\Unit
|
||||
{
|
||||
/**
|
||||
* @var \UnitTester
|
||||
*/
|
||||
public $tester;
|
||||
|
||||
public function testEmailIsSentOnContact()
|
||||
{
|
||||
$model = new ContactForm();
|
||||
|
||||
$model->attributes = [
|
||||
'name' => 'Tester',
|
||||
'email' => 'tester@example.com',
|
||||
'subject' => 'very important letter subject',
|
||||
'body' => 'body of current message',
|
||||
'verifyCode' => 'testme',
|
||||
];
|
||||
|
||||
verify($model->contact('admin@example.com'))->notEmpty();
|
||||
|
||||
// using Yii2 module actions to check email was sent
|
||||
$this->tester->seeEmailIsSent();
|
||||
|
||||
/** @var MessageInterface $emailMessage */
|
||||
$emailMessage = $this->tester->grabLastSentEmail();
|
||||
verify($emailMessage)->instanceOf('yii\mail\MessageInterface');
|
||||
verify($emailMessage->getTo())->arrayHasKey('admin@example.com');
|
||||
verify($emailMessage->getFrom())->arrayHasKey('noreply@example.com');
|
||||
verify($emailMessage->getReplyTo())->arrayHasKey('tester@example.com');
|
||||
verify($emailMessage->getSubject())->equals('very important letter subject');
|
||||
verify($emailMessage->toString())->stringContainsString('body of current message');
|
||||
}
|
||||
}
|
||||
51
tests/unit/models/LoginFormTest.php
Normal file
51
tests/unit/models/LoginFormTest.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace tests\unit\models;
|
||||
|
||||
use app\models\LoginForm;
|
||||
|
||||
class LoginFormTest extends \Codeception\Test\Unit
|
||||
{
|
||||
private $model;
|
||||
|
||||
protected function _after()
|
||||
{
|
||||
\Yii::$app->user->logout();
|
||||
}
|
||||
|
||||
public function testLoginNoUser()
|
||||
{
|
||||
$this->model = new LoginForm([
|
||||
'username' => 'not_existing_username',
|
||||
'password' => 'not_existing_password',
|
||||
]);
|
||||
|
||||
verify($this->model->login())->false();
|
||||
verify(\Yii::$app->user->isGuest)->true();
|
||||
}
|
||||
|
||||
public function testLoginWrongPassword()
|
||||
{
|
||||
$this->model = new LoginForm([
|
||||
'username' => 'demo',
|
||||
'password' => 'wrong_password',
|
||||
]);
|
||||
|
||||
verify($this->model->login())->false();
|
||||
verify(\Yii::$app->user->isGuest)->true();
|
||||
verify($this->model->errors)->arrayHasKey('password');
|
||||
}
|
||||
|
||||
public function testLoginCorrect()
|
||||
{
|
||||
$this->model = new LoginForm([
|
||||
'username' => 'demo',
|
||||
'password' => 'demo',
|
||||
]);
|
||||
|
||||
verify($this->model->login())->true();
|
||||
verify(\Yii::$app->user->isGuest)->false();
|
||||
verify($this->model->errors)->arrayHasNotKey('password');
|
||||
}
|
||||
|
||||
}
|
||||
44
tests/unit/models/UserTest.php
Normal file
44
tests/unit/models/UserTest.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace tests\unit\models;
|
||||
|
||||
use app\models\User;
|
||||
|
||||
class UserTest extends \Codeception\Test\Unit
|
||||
{
|
||||
public function testFindUserById()
|
||||
{
|
||||
verify($user = User::findIdentity(100))->notEmpty();
|
||||
verify($user->username)->equals('admin');
|
||||
|
||||
verify(User::findIdentity(999))->empty();
|
||||
}
|
||||
|
||||
public function testFindUserByAccessToken()
|
||||
{
|
||||
verify($user = User::findIdentityByAccessToken('100-token'))->notEmpty();
|
||||
verify($user->username)->equals('admin');
|
||||
|
||||
verify(User::findIdentityByAccessToken('non-existing'))->empty();
|
||||
}
|
||||
|
||||
public function testFindUserByUsername()
|
||||
{
|
||||
verify($user = User::findByUsername('admin'))->notEmpty();
|
||||
verify(User::findByUsername('not-admin'))->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testFindUserByUsername
|
||||
*/
|
||||
public function testValidateUser()
|
||||
{
|
||||
$user = User::findByUsername('admin');
|
||||
verify($user->validateAuthKey('test100key'))->notEmpty();
|
||||
verify($user->validateAuthKey('test102key'))->empty();
|
||||
|
||||
verify($user->validatePassword('admin'))->notEmpty();
|
||||
verify($user->validatePassword('123456'))->empty();
|
||||
}
|
||||
|
||||
}
|
||||
261
tests/unit/widgets/AlertTest.php
Normal file
261
tests/unit/widgets/AlertTest.php
Normal file
@ -0,0 +1,261 @@
|
||||
<?php
|
||||
|
||||
namespace tests\unit\widgets;
|
||||
|
||||
use app\widgets\Alert;
|
||||
use Yii;
|
||||
|
||||
class AlertTest extends \Codeception\Test\Unit
|
||||
{
|
||||
public function testSingleErrorMessage()
|
||||
{
|
||||
$message = 'This is an error message';
|
||||
|
||||
Yii::$app->session->setFlash('error', $message);
|
||||
|
||||
$renderingResult = Alert::widget();
|
||||
|
||||
verify($renderingResult)->stringContainsString($message);
|
||||
verify($renderingResult)->stringContainsString('alert-danger');
|
||||
|
||||
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||
}
|
||||
|
||||
public function testMultipleErrorMessages()
|
||||
{
|
||||
$firstMessage = 'This is the first error message';
|
||||
$secondMessage = 'This is the second error message';
|
||||
|
||||
Yii::$app->session->setFlash('error', [$firstMessage, $secondMessage]);
|
||||
|
||||
$renderingResult = Alert::widget();
|
||||
|
||||
verify($renderingResult)->stringContainsString($firstMessage);
|
||||
verify($renderingResult)->stringContainsString($secondMessage);
|
||||
verify($renderingResult)->stringContainsString('alert-danger');
|
||||
|
||||
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||
}
|
||||
|
||||
public function testSingleDangerMessage()
|
||||
{
|
||||
$message = 'This is a danger message';
|
||||
|
||||
Yii::$app->session->setFlash('danger', $message);
|
||||
|
||||
$renderingResult = Alert::widget();
|
||||
|
||||
verify($renderingResult)->stringContainsString($message);
|
||||
verify($renderingResult)->stringContainsString('alert-danger');
|
||||
|
||||
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||
}
|
||||
|
||||
public function testMultipleDangerMessages()
|
||||
{
|
||||
$firstMessage = 'This is the first danger message';
|
||||
$secondMessage = 'This is the second danger message';
|
||||
|
||||
Yii::$app->session->setFlash('danger', [$firstMessage, $secondMessage]);
|
||||
|
||||
$renderingResult = Alert::widget();
|
||||
|
||||
verify($renderingResult)->stringContainsString($firstMessage);
|
||||
verify($renderingResult)->stringContainsString($secondMessage);
|
||||
verify($renderingResult)->stringContainsString('alert-danger');
|
||||
|
||||
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||
}
|
||||
|
||||
public function testSingleSuccessMessage()
|
||||
{
|
||||
$message = 'This is a success message';
|
||||
|
||||
Yii::$app->session->setFlash('success', $message);
|
||||
|
||||
$renderingResult = Alert::widget();
|
||||
|
||||
verify($renderingResult)->stringContainsString($message);
|
||||
verify($renderingResult)->stringContainsString('alert-success');
|
||||
|
||||
verify($renderingResult)->stringNotContainsString('alert-danger');
|
||||
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||
}
|
||||
|
||||
public function testMultipleSuccessMessages()
|
||||
{
|
||||
$firstMessage = 'This is the first danger message';
|
||||
$secondMessage = 'This is the second danger message';
|
||||
|
||||
Yii::$app->session->setFlash('success', [$firstMessage, $secondMessage]);
|
||||
|
||||
$renderingResult = Alert::widget();
|
||||
|
||||
verify($renderingResult)->stringContainsString($firstMessage);
|
||||
verify($renderingResult)->stringContainsString($secondMessage);
|
||||
verify($renderingResult)->stringContainsString('alert-success');
|
||||
|
||||
verify($renderingResult)->stringNotContainsString('alert-danger');
|
||||
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||
}
|
||||
|
||||
public function testSingleInfoMessage()
|
||||
{
|
||||
$message = 'This is an info message';
|
||||
|
||||
Yii::$app->session->setFlash('info', $message);
|
||||
|
||||
$renderingResult = Alert::widget();
|
||||
|
||||
verify($renderingResult)->stringContainsString($message);
|
||||
verify($renderingResult)->stringContainsString('alert-info');
|
||||
|
||||
verify($renderingResult)->stringNotContainsString('alert-danger');
|
||||
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||
}
|
||||
|
||||
public function testMultipleInfoMessages()
|
||||
{
|
||||
$firstMessage = 'This is the first info message';
|
||||
$secondMessage = 'This is the second info message';
|
||||
|
||||
Yii::$app->session->setFlash('info', [$firstMessage, $secondMessage]);
|
||||
|
||||
$renderingResult = Alert::widget();
|
||||
|
||||
verify($renderingResult)->stringContainsString($firstMessage);
|
||||
verify($renderingResult)->stringContainsString($secondMessage);
|
||||
verify($renderingResult)->stringContainsString('alert-info');
|
||||
|
||||
verify($renderingResult)->stringNotContainsString('alert-danger');
|
||||
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||
}
|
||||
|
||||
public function testSingleWarningMessage()
|
||||
{
|
||||
$message = 'This is a warning message';
|
||||
|
||||
Yii::$app->session->setFlash('warning', $message);
|
||||
|
||||
$renderingResult = Alert::widget();
|
||||
|
||||
verify($renderingResult)->stringContainsString($message);
|
||||
verify($renderingResult)->stringContainsString('alert-warning');
|
||||
|
||||
verify($renderingResult)->stringNotContainsString('alert-danger');
|
||||
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||
}
|
||||
|
||||
public function testMultipleWarningMessages()
|
||||
{
|
||||
$firstMessage = 'This is the first warning message';
|
||||
$secondMessage = 'This is the second warning message';
|
||||
|
||||
Yii::$app->session->setFlash('warning', [$firstMessage, $secondMessage]);
|
||||
|
||||
$renderingResult = Alert::widget();
|
||||
|
||||
verify($renderingResult)->stringContainsString($firstMessage);
|
||||
verify($renderingResult)->stringContainsString($secondMessage);
|
||||
verify($renderingResult)->stringContainsString('alert-warning');
|
||||
|
||||
verify($renderingResult)->stringNotContainsString('alert-danger');
|
||||
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||
}
|
||||
|
||||
public function testSingleMixedMessages() {
|
||||
$errorMessage = 'This is an error message';
|
||||
$dangerMessage = 'This is a danger message';
|
||||
$successMessage = 'This is a success message';
|
||||
$infoMessage = 'This is a info message';
|
||||
$warningMessage = 'This is a warning message';
|
||||
|
||||
Yii::$app->session->setFlash('error', $errorMessage);
|
||||
Yii::$app->session->setFlash('danger', $dangerMessage);
|
||||
Yii::$app->session->setFlash('success', $successMessage);
|
||||
Yii::$app->session->setFlash('info', $infoMessage);
|
||||
Yii::$app->session->setFlash('warning', $warningMessage);
|
||||
|
||||
$renderingResult = Alert::widget();
|
||||
|
||||
verify($renderingResult)->stringContainsString($errorMessage);
|
||||
verify($renderingResult)->stringContainsString($dangerMessage);
|
||||
verify($renderingResult)->stringContainsString($successMessage);
|
||||
verify($renderingResult)->stringContainsString($infoMessage);
|
||||
verify($renderingResult)->stringContainsString($warningMessage);
|
||||
|
||||
verify($renderingResult)->stringContainsString('alert-danger');
|
||||
verify($renderingResult)->stringContainsString('alert-success');
|
||||
verify($renderingResult)->stringContainsString('alert-info');
|
||||
verify($renderingResult)->stringContainsString('alert-warning');
|
||||
}
|
||||
|
||||
public function testMultipleMixedMessages() {
|
||||
$firstErrorMessage = 'This is the first error message';
|
||||
$secondErrorMessage = 'This is the second error message';
|
||||
$firstDangerMessage = 'This is the first danger message';
|
||||
$secondDangerMessage = 'This is the second';
|
||||
$firstSuccessMessage = 'This is the first success message';
|
||||
$secondSuccessMessage = 'This is the second success message';
|
||||
$firstInfoMessage = 'This is the first info message';
|
||||
$secondInfoMessage = 'This is the second info message';
|
||||
$firstWarningMessage = 'This is the first warning message';
|
||||
$secondWarningMessage = 'This is the second warning message';
|
||||
|
||||
Yii::$app->session->setFlash('error', [$firstErrorMessage, $secondErrorMessage]);
|
||||
Yii::$app->session->setFlash('danger', [$firstDangerMessage, $secondDangerMessage]);
|
||||
Yii::$app->session->setFlash('success', [$firstSuccessMessage, $secondSuccessMessage]);
|
||||
Yii::$app->session->setFlash('info', [$firstInfoMessage, $secondInfoMessage]);
|
||||
Yii::$app->session->setFlash('warning', [$firstWarningMessage, $secondWarningMessage]);
|
||||
|
||||
$renderingResult = Alert::widget();
|
||||
|
||||
verify($renderingResult)->stringContainsString($firstErrorMessage);
|
||||
verify($renderingResult)->stringContainsString($secondErrorMessage);
|
||||
verify($renderingResult)->stringContainsString($firstDangerMessage);
|
||||
verify($renderingResult)->stringContainsString($secondDangerMessage);
|
||||
verify($renderingResult)->stringContainsString($firstSuccessMessage);
|
||||
verify($renderingResult)->stringContainsString($secondSuccessMessage);
|
||||
verify($renderingResult)->stringContainsString($firstInfoMessage);
|
||||
verify($renderingResult)->stringContainsString($secondInfoMessage);
|
||||
verify($renderingResult)->stringContainsString($firstWarningMessage);
|
||||
verify($renderingResult)->stringContainsString($secondWarningMessage);
|
||||
|
||||
verify($renderingResult)->stringContainsString('alert-danger');
|
||||
verify($renderingResult)->stringContainsString('alert-success');
|
||||
verify($renderingResult)->stringContainsString('alert-info');
|
||||
verify($renderingResult)->stringContainsString('alert-warning');
|
||||
}
|
||||
|
||||
public function testFlashIntegrity()
|
||||
{
|
||||
$errorMessage = 'This is an error message';
|
||||
$unrelatedMessage = 'This is a message that is not related to the alert widget';
|
||||
|
||||
Yii::$app->session->setFlash('error', $errorMessage);
|
||||
Yii::$app->session->setFlash('unrelated', $unrelatedMessage);
|
||||
|
||||
Alert::widget();
|
||||
|
||||
// Simulate redirect
|
||||
Yii::$app->session->close();
|
||||
Yii::$app->session->open();
|
||||
|
||||
verify(Yii::$app->session->getFlash('error'))->empty();
|
||||
verify(Yii::$app->session->getFlash('unrelated'))->equals($unrelatedMessage);
|
||||
}
|
||||
}
|
||||
BIN
vagrant/.DS_Store
vendored
Normal file
BIN
vagrant/.DS_Store
vendored
Normal file
Binary file not shown.
2
vagrant/config/.gitignore
vendored
Normal file
2
vagrant/config/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# local configuration
|
||||
vagrant-local.yml
|
||||
22
vagrant/config/vagrant-local.example.yml
Normal file
22
vagrant/config/vagrant-local.example.yml
Normal file
@ -0,0 +1,22 @@
|
||||
# Your personal GitHub token
|
||||
github_token: <your-personal-github-token>
|
||||
# Read more: https://github.com/blog/1509-personal-api-tokens
|
||||
# You can generate it here: https://github.com/settings/tokens
|
||||
|
||||
# Guest OS timezone
|
||||
timezone: Europe/London
|
||||
|
||||
# Are we need check box updates for every 'vagrant up'?
|
||||
box_check_update: false
|
||||
|
||||
# Virtual machine name
|
||||
machine_name: yii2basic
|
||||
|
||||
# Virtual machine IP
|
||||
ip: 192.168.83.137
|
||||
|
||||
# Virtual machine CPU cores number
|
||||
cpus: 1
|
||||
|
||||
# Virtual machine RAM
|
||||
memory: 1024
|
||||
38
vagrant/nginx/app.conf
Normal file
38
vagrant/nginx/app.conf
Normal file
@ -0,0 +1,38 @@
|
||||
server {
|
||||
charset utf-8;
|
||||
client_max_body_size 128M;
|
||||
sendfile off;
|
||||
|
||||
listen 80; ## listen for ipv4
|
||||
#listen [::]:80 default_server ipv6only=on; ## listen for ipv6
|
||||
|
||||
server_name yii2basic.test;
|
||||
root /app/web/;
|
||||
index index.php;
|
||||
|
||||
access_log /app/vagrant/nginx/log/yii2basic.access.log;
|
||||
error_log /app/vagrant/nginx/log/yii2basic.error.log;
|
||||
|
||||
location / {
|
||||
# Redirect everything that isn't a real file to index.php
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
}
|
||||
|
||||
# uncomment to avoid processing of calls to non-existing static files by Yii
|
||||
#location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
|
||||
# try_files $uri =404;
|
||||
#}
|
||||
#error_page 404 /404.html;
|
||||
|
||||
location ~ \.php$ {
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
#fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location ~ /\.(ht|svn|git) {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
3
vagrant/nginx/log/.gitignore
vendored
Normal file
3
vagrant/nginx/log/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
#nginx logs
|
||||
yii2basic.access.log
|
||||
yii2basic.error.log
|
||||
18
vagrant/provision/always-as-root.sh
Normal file
18
vagrant/provision/always-as-root.sh
Normal file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#== Bash helpers ==
|
||||
|
||||
function info {
|
||||
echo " "
|
||||
echo "--> $1"
|
||||
echo " "
|
||||
}
|
||||
|
||||
#== Provision script ==
|
||||
|
||||
info "Provision-script user: `whoami`"
|
||||
|
||||
info "Restart web-stack"
|
||||
service php7.2-fpm restart
|
||||
service nginx restart
|
||||
service mysql restart
|
||||
79
vagrant/provision/once-as-root.sh
Normal file
79
vagrant/provision/once-as-root.sh
Normal file
@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#== Import script args ==
|
||||
|
||||
timezone=$(echo "$1")
|
||||
readonly IP=$2
|
||||
|
||||
#== Bash helpers ==
|
||||
|
||||
function info {
|
||||
echo " "
|
||||
echo "--> $1"
|
||||
echo " "
|
||||
}
|
||||
|
||||
#== Provision script ==
|
||||
|
||||
info "Provision-script user: `whoami`"
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
info "Configure timezone"
|
||||
timedatectl set-timezone ${timezone} --no-ask-password
|
||||
|
||||
info "Add the VM IP to the list of allowed IPs"
|
||||
awk -v ip=$IP -f /app/vagrant/provision/provision.awk /app/config/web.php
|
||||
|
||||
info "Prepare root password for MySQL"
|
||||
debconf-set-selections <<< 'mariadb-server mysql-server/root_password password'
|
||||
debconf-set-selections <<< 'mariadb-server mysql-server/root_password_again password'
|
||||
echo "Done!"
|
||||
|
||||
info "Update OS software"
|
||||
apt-get update
|
||||
apt-get upgrade -y
|
||||
|
||||
info "Install additional software"
|
||||
apt-get install -y php7.2-curl php7.2-cli php7.2-intl php7.2-mysqlnd php7.2-gd php7.2-fpm php7.2-mbstring php7.2-xml unzip nginx mariadb-server-10.1 php.xdebug
|
||||
|
||||
info "Configure MySQL"
|
||||
sed -i 's/.*bind-address.*/bind-address = 0.0.0.0/' /etc/mysql/mariadb.conf.d/50-server.cnf
|
||||
mysql <<< "CREATE USER 'root'@'%' IDENTIFIED BY ''"
|
||||
mysql <<< "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'"
|
||||
mysql <<< "DROP USER 'root'@'localhost'"
|
||||
mysql <<< 'FLUSH PRIVILEGES'
|
||||
echo "Done!"
|
||||
|
||||
info "Configure PHP-FPM"
|
||||
sed -i 's/user = www-data/user = vagrant/g' /etc/php/7.2/fpm/pool.d/www.conf
|
||||
sed -i 's/group = www-data/group = vagrant/g' /etc/php/7.2/fpm/pool.d/www.conf
|
||||
sed -i 's/owner = www-data/owner = vagrant/g' /etc/php/7.2/fpm/pool.d/www.conf
|
||||
cat << EOF > /etc/php/7.2/mods-available/xdebug.ini
|
||||
zend_extension=xdebug.so
|
||||
xdebug.remote_enable=1
|
||||
xdebug.remote_connect_back=1
|
||||
xdebug.remote_port=9000
|
||||
xdebug.remote_autostart=1
|
||||
EOF
|
||||
echo "Done!"
|
||||
|
||||
info "Configure NGINX"
|
||||
sed -i 's/user www-data/user vagrant/g' /etc/nginx/nginx.conf
|
||||
echo "Done!"
|
||||
|
||||
info "Enabling site configuration"
|
||||
ln -s /app/vagrant/nginx/app.conf /etc/nginx/sites-enabled/app.conf
|
||||
echo "Done!"
|
||||
|
||||
info "Removing default site configuration"
|
||||
rm /etc/nginx/sites-enabled/default
|
||||
echo "Done!"
|
||||
|
||||
info "Initialize databases for MySQL"
|
||||
mysql <<< 'CREATE DATABASE yii2basic'
|
||||
mysql <<< 'CREATE DATABASE yii2basic_test'
|
||||
echo "Done!"
|
||||
|
||||
info "Install composer"
|
||||
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||
31
vagrant/provision/once-as-vagrant.sh
Normal file
31
vagrant/provision/once-as-vagrant.sh
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#== Import script args ==
|
||||
|
||||
github_token=$(echo "$1")
|
||||
|
||||
#== Bash helpers ==
|
||||
|
||||
function info {
|
||||
echo " "
|
||||
echo "--> $1"
|
||||
echo " "
|
||||
}
|
||||
|
||||
#== Provision script ==
|
||||
|
||||
info "Provision-script user: `whoami`"
|
||||
|
||||
info "Configure composer"
|
||||
composer config --global github-oauth.github.com ${github_token}
|
||||
echo "Done!"
|
||||
|
||||
info "Install project dependencies"
|
||||
cd /app
|
||||
composer --no-progress --prefer-dist install
|
||||
|
||||
info "Create bash-alias 'app' for vagrant user"
|
||||
echo 'alias app="cd /app"' | tee /home/vagrant/.bash_aliases
|
||||
|
||||
info "Enabling colorized prompt for guest console"
|
||||
sed -i "s/#force_color_prompt=yes/force_color_prompt=yes/" /home/vagrant/.bashrc
|
||||
50
vagrant/provision/provision.awk
Normal file
50
vagrant/provision/provision.awk
Normal file
@ -0,0 +1,50 @@
|
||||
###
|
||||
# Modifying Yii2's files for Vagrant VM
|
||||
#
|
||||
# @author HA3IK <golubha3ik@gmail.com>
|
||||
# @version 1.0.0
|
||||
|
||||
BEGIN {
|
||||
print "AWK BEGINs its work:"
|
||||
IGNORECASE = 1
|
||||
|
||||
# Correct IP - wildcard last octet
|
||||
match(ip, /(([0-9]+\.)+)/, arr)
|
||||
ip = arr[1] "*"
|
||||
}
|
||||
# BODY
|
||||
{
|
||||
# Check if it's the same file
|
||||
if (FILENAME != isFile["same"]){
|
||||
msg = "- Work with: " FILENAME
|
||||
# Close a previous file
|
||||
close(isFile["same"])
|
||||
# Delete previous data
|
||||
delete isFile
|
||||
# Save current file
|
||||
isFile["same"] = FILENAME
|
||||
# Define array index for the file
|
||||
switch (FILENAME){
|
||||
case /config\/web\.php$/:
|
||||
isFile["IsConfWeb"] = 1
|
||||
msg = msg " - add allowed IP: " ip
|
||||
break
|
||||
}
|
||||
# Print the concatenated message for the file
|
||||
print msg
|
||||
}
|
||||
|
||||
# IF config/web.php
|
||||
if (isFile["IsConfWeb"]){
|
||||
# IF line has "allowedIPs" and doesn't has our IP
|
||||
if (match($0, "allowedIPs") && !match($0, ip)){
|
||||
match($0, /([^\]]+)(.+)/, arr)
|
||||
$0 = sprintf("%s, '%s'%s", arr[1], ip, arr[2])
|
||||
}
|
||||
# Rewrite the file
|
||||
print $0 > FILENAME
|
||||
}
|
||||
}
|
||||
END {
|
||||
print "AWK ENDs its work."
|
||||
}
|
||||
22
vendor/autoload.php
vendored
Normal file
22
vendor/autoload.php
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException($err);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit5de2040ea43272234872b658d6c40998::getLoader();
|
||||
605
vendor/behat/gherkin/CHANGELOG.md
vendored
Normal file
605
vendor/behat/gherkin/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,605 @@
|
||||
# Change Log
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
This project follows the [Behat release and version support policies]
|
||||
(https://docs.behat.org/en/latest/releases.html).
|
||||
|
||||
# [4.16.1] - 2025-12-08
|
||||
|
||||
### Fixed
|
||||
|
||||
* Reinstate support for tag filter expressions without a leading `@` (e.g. `wip&&~slow` instead of `@wip&&~@slow`).
|
||||
This syntax was never officially supported, but previously worked and was broken by 4.16.0. We have temporarily
|
||||
fixed this, but it is deprecated and will be removed in the next major version.
|
||||
|
||||
# [4.16.0] - 2025-12-05
|
||||
|
||||
### Changed
|
||||
|
||||
* Further improvements to parser parity when the experimental `gherkin-32` compatibility mode is enabled:
|
||||
- Parse descriptions (instead of multiline titles) for all describable nodes by @acoulton in [#361](https://github.com/Behat/Gherkin/pull/361)
|
||||
- Unescape escaped delimiters within doc strings by @stof in [#393](https://github.com/Behat/Gherkin/pull/393)
|
||||
- Retain the `@` prefix when parsing tags by @acoulton in [#400](https://github.com/Behat/Gherkin/pull/400)
|
||||
- Trim unicode padding from table cells by @stof in [#405](https://github.com/Behat/Gherkin/pull/405)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix the implementation of the default dialect for the keywords provider by @stof in [#404](https://github.com/Behat/Gherkin/pull/404)
|
||||
|
||||
### Internal
|
||||
|
||||
* Add `Stringable` to classes implementing __toString() by @acoulton in [#402](https://github.com/Behat/Gherkin/pull/402)
|
||||
* Fix cucumber variant assertions to include inherited properties by @acoulton in [#394](https://github.com/Behat/Gherkin/pull/394)
|
||||
* Update cucumber/gherkin parity tests to 37.0.0 by @behat-gherkin-updater[bot] in [#397](https://github.com/Behat/Gherkin/pull/397) and [#398](https://github.com/Behat/Gherkin/pull/398)
|
||||
* update_cucumber script should not fail on manually created releases by @acoulton in [#396](https://github.com/Behat/Gherkin/pull/396)
|
||||
* Add funding links and information by @acoulton in [#401](https://github.com/Behat/Gherkin/pull/401)
|
||||
|
||||
# [4.15.0] - 2025-11-05
|
||||
|
||||
### Changed
|
||||
|
||||
* Added a new ParserInterface and deprecated extending the core Lexer, Parser and Node classes by @acoulton in [#354](https://github.com/Behat/Gherkin/pull/354)
|
||||
* Deprecate the CucumberNDJsonAstLoader (which was only intended for internal use by our tests) by @stof in [#356](https://github.com/Behat/Gherkin/pull/356)
|
||||
* By default, the parser ignores invalid language tags (e.g. `#language:no-such`) and falls back to the default language
|
||||
(e.g. `en`). Previously, the resultant `FeatureNode::getLanguage()` would return the original invalid value from the
|
||||
feature file - it will now return the language that was actually used for parsing. By @stof in [#350](https://github.com/Behat/Gherkin/pull/350)
|
||||
|
||||
### Added
|
||||
|
||||
* Introduce a DialectProviderInterface matching the modern cucumber API. This will replace the existing Keywords API in
|
||||
a future major release. By @stof in [#350](https://github.com/Behat/Gherkin/pull/350)
|
||||
* Introduce configurable `GherkinCompatibilityMode` to control how gherkin files are parsed. In the default `legacy` mode,
|
||||
there is no change to parsing. In the new **experimental** `gherkin-32` mode, files will in future be parsed
|
||||
consistently with the official cucumber/gherkin parsers. This mode is not yet complete - in this first release:
|
||||
- Whitespace within description nodes will not be trimmed by @acoulton in [#349](https://github.com/Behat/Gherkin/pull/349)
|
||||
- Invalid language tags will cause an exception by @stof in [#357](https://github.com/Behat/Gherkin/pull/357)
|
||||
- Step keywords will not be trimmed by @stof in [#360](https://github.com/Behat/Gherkin/pull/360)
|
||||
- Language tags can include whitespace by @acoulton in [#358](https://github.com/Behat/Gherkin/pull/358)
|
||||
- `\n` literals in table cells will be parsed as newlines by @stof in [#359](https://github.com/Behat/Gherkin/pull/359)
|
||||
and [#391](https://github.com/Behat/Gherkin/pull/391)
|
||||
* Improved translations for `ru` (Russian) and `af` (Afrikaans) from cucumber/gherkin in [#381](https://github.com/Behat/Gherkin/pull/381)
|
||||
and [#386](https://github.com/Behat/Gherkin/pull/386)
|
||||
* Support PHP 8.5 by @acoulton in [#388](https://github.com/Behat/Gherkin/pull/388)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Improve phpdoc / phpstan type-hinting of the lexer and parser by @uuf6429 in [#344](https://github.com/Behat/Gherkin/pull/344)
|
||||
and @stof in [#363](https://github.com/Behat/Gherkin/pull/363)
|
||||
* Handle race conditions when creating cache directory by @uuf6429 in [#373](https://github.com/Behat/Gherkin/pull/373)
|
||||
* Throw if Loader->load() called with unsupported resource by @uuf6429 in [#372](https://github.com/Behat/Gherkin/pull/372)
|
||||
* Use default file cache key if `behat/gherkin` version is unknown by @uuf6429 in [#370](https://github.com/Behat/Gherkin/pull/370)
|
||||
|
||||
### Internal
|
||||
|
||||
* Enable PHPStan level 10 and resolve remaining warnings by @uuf6429 in [#368](https://github.com/Behat/Gherkin/pull/368)
|
||||
* Remove duplication and improve robustness in filesystem operations by @uuf6429 in [#365](https://github.com/Behat/Gherkin/pull/365)
|
||||
and [#367](https://github.com/Behat/Gherkin/pull/367)
|
||||
* Explicitly cover expected departures from cucumber gherkin parsing with tests by @acoulton in [#392](https://github.com/Behat/Gherkin/pull/392)
|
||||
* Update cucumber/gherkin parity tests to v36.0.0 in [#355](https://github.com/Behat/Gherkin/pull/355), [#376](https://github.com/Behat/Gherkin/pull/376)
|
||||
[#378](https://github.com/Behat/Gherkin/pull/378), [#381](https://github.com/Behat/Gherkin/pull/381), [#385](https://github.com/Behat/Gherkin/pull/385)
|
||||
[#386](https://github.com/Behat/Gherkin/pull/386) and [#387](https://github.com/Behat/Gherkin/pull/387)
|
||||
* Fixes and improvements to the cucumber update CI job by @acoulton in [#374](https://github.com/Behat/Gherkin/pull/374),
|
||||
[#375](https://github.com/Behat/Gherkin/pull/375), [#379](https://github.com/Behat/Gherkin/pull/379)
|
||||
and [#380](https://github.com/Behat/Gherkin/pull/380)
|
||||
* Minor coding style fixes by @acoulton in [#377](https://github.com/Behat/Gherkin/pull/377) and [#383](https://github.com/Behat/Gherkin/pull/383)
|
||||
* Minor code improvements to Lexer/Parser implementation by @uuf6429 in [#352](https://github.com/Behat/Gherkin/pull/352)
|
||||
* Minor code improvements to TableNode by @uuf6429 in [#366](https://github.com/Behat/Gherkin/pull/366)
|
||||
* Add native typehints where this does not break BC by @stof in [#353](https://github.com/Behat/Gherkin/pull/353)
|
||||
* Fix typo of a PHPStan alias type by @uuf6429 in [#371](https://github.com/Behat/Gherkin/pull/371)
|
||||
* Fix github actions workflow job name by @uuf6429 in [#369](https://github.com/Behat/Gherkin/pull/369)
|
||||
|
||||
# [4.14.0] - 2025-05-23
|
||||
|
||||
### Changed
|
||||
|
||||
* Throw ParserException if file ends with tags by @acoulton in [#313](https://github.com/Behat/Gherkin/pull/313)
|
||||
* Throw ParserException if Background comes after first Scenario by @acoulton in [#343](https://github.com/Behat/Gherkin/pull/343)
|
||||
* For compatibility with the official cucumber/gherkin parsers, we now accept some gherkin syntax that would previously
|
||||
have triggered a ParserException. Users may wish to consider running a tool like gherkin-lint in CI to detect
|
||||
incomplete feature files or valid-but-unusual gherkin syntax. The specific changes are:
|
||||
- Parse `Scenario` and `Scenario Outline` as synonyms depending on the presence (or not) of an `Examples:` keyword.
|
||||
by @acoulton in [#316](https://github.com/Behat/Gherkin/pull/316) and [#324](https://github.com/Behat/Gherkin/pull/324)
|
||||
- Do not throw on some unexpected Feature / Language tags by @acoulton in [#323](https://github.com/Behat/Gherkin/pull/323)
|
||||
- Do not throw on `.feature` file that does not contain a Feature by @acoulton in [#340](https://github.com/Behat/Gherkin/pull/340)
|
||||
- Ignore content after table right-hand `|` (instead of throwing) by @acoulton in [#341](https://github.com/Behat/Gherkin/pull/341)
|
||||
* Remove the line length from the NewLine token value by @stof in [#338](https://github.com/Behat/Gherkin/pull/338)
|
||||
* Added precise PHPStan type information by @stof in [#332](https://github.com/Behat/Gherkin/pull/332),
|
||||
[#333](https://github.com/Behat/Gherkin/pull/333), [#339](https://github.com/Behat/Gherkin/pull/339)
|
||||
and [#334](https://github.com/Behat/Gherkin/pull/334)
|
||||
|
||||
### Internal
|
||||
|
||||
* Make private props readonly; fix tests by @uuf6429 in [#319](https://github.com/Behat/Gherkin/pull/319)
|
||||
* Use the `Yaml::parseFile` API to handle Yaml files by @stof in [#335](https://github.com/Behat/Gherkin/pull/335)
|
||||
* test: Make CucumberND name reading consistent by @uuf6429 in [#309](https://github.com/Behat/Gherkin/pull/309)
|
||||
* test: Use vfsStream to simplify / improve filesystem-related tests by @uuf6429 in [#298](https://github.com/Behat/Gherkin/pull/298)
|
||||
* test: Handle optional tableHeader when loading NDJson examples by @uuf6429 in [#294](https://github.com/Behat/Gherkin/pull/294)
|
||||
* test: Refactor valid ParserExceptionsTest examples into cucumber/gherkin testdata by @acoulton in [#322](https://github.com/Behat/Gherkin/pull/322)
|
||||
* test: Compare step arguments when checking gherkin parity by @acoulton in [#325](https://github.com/Behat/Gherkin/pull/325)
|
||||
* test: Use a custom object comparator to ignore the keywordType of StepNode by @stof in [#331](https://github.com/Behat/Gherkin/pull/331)
|
||||
* ci: Add conventional title to gherkin update, error on missing asserts by @acoulton in [#314](https://github.com/Behat/Gherkin/pull/314)
|
||||
* Assert that preg_split does not fail when splitting a table row by @stof in [#337](https://github.com/Behat/Gherkin/pull/337)
|
||||
* Add assertions in the parser to reflect the structure of tokens by @stof in [#342](https://github.com/Behat/Gherkin/pull/342)
|
||||
* style: Define and change phpdoc order coding style by @uuf6429 in [#345](https://github.com/Behat/Gherkin/pull/345)
|
||||
|
||||
|
||||
# [4.13.0] - 2025-05-06
|
||||
|
||||
### Changed
|
||||
|
||||
* Files have been moved to flatten paths into a PSR-4 structure (instead of the previous PSR-0). This may affect users
|
||||
who are requiring files directly rather than using the composer autoloader as expected.
|
||||
See the 4.12.0 release for the new `CachedArrayKeywords::withDefaultKeywords()` to use the `i18n.php` file without
|
||||
depending on paths to other files in this repo. By @uuf6429 in [#288](https://github.com/Behat/Gherkin/pull/288)
|
||||
|
||||
### Added
|
||||
|
||||
* ExampleTableNode now implements TaggedNodeInterface. Also refactored node tag handling methods. By @uuf6429 in
|
||||
[#289](https://github.com/Behat/Gherkin/pull/289)
|
||||
* Improve some exceptions thrown when parsing invalid feature files. Also increased test coverage. By @uuf6429 in
|
||||
[#295](https://github.com/Behat/Gherkin/pull/295)
|
||||
* New translations for `amh` (Amharic), `be` (Belarusian) and `ml` (Malayalam) from cucumber/gherkin in [#306](https://github.com/Behat/Gherkin/pull/306)
|
||||
* Improved translations / whitespace for `ga` (Irish), `it` (Italian), `ja` (Japanese), `ka` (Georgian) and `ko` (Korean)
|
||||
from cucumber/gherkin in [#306](https://github.com/Behat/Gherkin/pull/306)
|
||||
|
||||
### Internal
|
||||
|
||||
* Fix & improve automatic CI updates to newer cucumber/gherkin test data and translations. By @acoulton in
|
||||
[#300](https://github.com/Behat/Gherkin/pull/300), [#302](https://github.com/Behat/Gherkin/pull/302),
|
||||
[#304](https://github.com/Behat/Gherkin/pull/304), [#305](https://github.com/Behat/Gherkin/pull/305)
|
||||
* Update code style and resolve PHPStan warnings (up to level 9) in tests and CI scripts. By @uuf6429 in
|
||||
[#296](https://github.com/Behat/Gherkin/pull/296), [#297](https://github.com/Behat/Gherkin/pull/297)
|
||||
and [#307](https://github.com/Behat/Gherkin/pull/307)
|
||||
* Make tests that expect exceptions more explicit by @uuf6429 in [#310](https://github.com/Behat/Gherkin/pull/310)
|
||||
* Improve CI workflows and integrate Codecov reporting by @uuf6429 in [#299](https://github.com/Behat/Gherkin/pull/299)
|
||||
and [#301](https://github.com/Behat/Gherkin/pull/301)
|
||||
* Refactor tag filtering implementation by @uuf6429 in [#308](https://github.com/Behat/Gherkin/pull/308)
|
||||
* Update cucumber/gherkin parity tests to v32.1.1 in [#306](https://github.com/Behat/Gherkin/pull/306)
|
||||
|
||||
# [4.12.0] - 2025-02-26
|
||||
|
||||
### Changed
|
||||
* Gherkin::VERSION is deprecated and will not be updated, use the composer runtime API if you need to identify the
|
||||
running version. This also changes the value used to namespace cached feature files.
|
||||
by @acoulton in [#279](https://github.com/Behat/Gherkin/pull/279)
|
||||
|
||||
### Added
|
||||
|
||||
* Provide `CachedArrayKeywords::withDefaultKeywords()` to create an instance without an external dependency on the path
|
||||
to the `i18n.php` file in this repo. **NOTE** that paths to source files will change in the next Gherkin release -
|
||||
use the new constructor to avoid any impact.
|
||||
by @carlos-granados in [#290](https://github.com/Behat/Gherkin/pull/290)
|
||||
|
||||
### Internal
|
||||
|
||||
* Upgrade to phpunit 10 by @uuf6429 in [#275](https://github.com/Behat/Gherkin/pull/275)
|
||||
* Remove redundant files by @uuf6429 in [#278](https://github.com/Behat/Gherkin/pull/278)
|
||||
* Update documentation by @uuf6429 in [#274](https://github.com/Behat/Gherkin/pull/274)
|
||||
* Adopt PHP CS Fixer and apply code styles by @uuf6429 in [#277](https://github.com/Behat/Gherkin/pull/277)
|
||||
* Add PHPStan and improve / fix docblock annotations and type-safety within methods to achieve level 5 by
|
||||
@uuf6429 in [#276](https://github.com/Behat/Gherkin/pull/276), [#281](https://github.com/Behat/Gherkin/pull/281),
|
||||
[#282](https://github.com/Behat/Gherkin/pull/282), and [#287](https://github.com/Behat/Gherkin/pull/287)
|
||||
|
||||
# [4.11.0] - 2024-12-06
|
||||
|
||||
### Changed
|
||||
|
||||
* Drop support for PHP < 8.1, Symfony < 5.4 and Symfony 6.0 - 6.3. In future we will drop support for PHP and symfony
|
||||
versions as they reach EOL. by @acoulton in [#272](https://github.com/Behat/Gherkin/pull/272)
|
||||
* Deprecated `ExampleNode::getTitle()` and `ScenarioNode::getTitle()` in favour of new methods with clearer meaning.
|
||||
by @uuf6429 in [#271](https://github.com/Behat/Gherkin/pull/271)
|
||||
|
||||
### Added
|
||||
|
||||
* Added `(ExampleNode|ScenarioNode)::getName()` to access human-readable names for examples and scenarios,
|
||||
and `ExampleNode::getExampleText()` for the string content of the example table row.
|
||||
by @uuf6429 in [#271](https://github.com/Behat/Gherkin/pull/271)
|
||||
|
||||
### Internal
|
||||
|
||||
* Enable dependabot for github actions workflows by @jrfnl in [#261](https://github.com/Behat/Gherkin/pull/261)
|
||||
|
||||
# 4.10.0 / 2024-10-19
|
||||
|
||||
### Changed
|
||||
|
||||
- **⚠ Backslashes in feature files must now be escaped**\
|
||||
Gherkin syntax treats `\` as an escape character, which must be escaped (`\\`) to use it as a
|
||||
literal value. Historically, this was not being parsed correctly. This release fixes that bug,
|
||||
but means that if your scenarios currently use unescaped `\` you will need to replace each one
|
||||
with `\\` to achieve the same parsed result.
|
||||
By @everzet in 5a0836d.
|
||||
|
||||
### Added
|
||||
- Symfony 6 and 7 thanks to @tacman in #257
|
||||
- PHP 8.4 support thanks to @heiglandreas in #258 and @jrfnl in #262
|
||||
|
||||
### Fixed
|
||||
- Fix exception when filter string is empty thanks to @magikid in #251
|
||||
|
||||
### Internal
|
||||
- Sync teststuite with Cucumber 24.1.0
|
||||
- Fix PHPUnit 10 deprecation messages
|
||||
- A lot of great CI work by @heiglandreas and @jrfnl
|
||||
|
||||
# 4.9.0 / 2021-10-12
|
||||
|
||||
- Simplify the boolean condition for the tag matching by @stof in https://github.com/Behat/Gherkin/pull/219
|
||||
- Remove symfony phpunit bridge by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/220
|
||||
- Ignore the bin folder in archives by @stof in https://github.com/Behat/Gherkin/pull/226
|
||||
- Cast table node exceptions into ParserExceptions when throwing by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/216
|
||||
- Cucumber changelog in PRs and using correct hash by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/225
|
||||
- Support alternative docstrings format (```) by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/214
|
||||
- Fix DocBlocks (Boolean -> bool) by @simonhammes in https://github.com/Behat/Gherkin/pull/237
|
||||
- Tag parsing by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/215
|
||||
- Remove test - cucumber added an example with Rule which is not supported by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/239
|
||||
- Add PHP 8.1 support by @javer in https://github.com/Behat/Gherkin/pull/242
|
||||
- Fix main branch alias version by @mvorisek in https://github.com/Behat/Gherkin/pull/244
|
||||
|
||||
# 4.8.0 / 2021-02-04
|
||||
|
||||
- Drop support for PHP before version 7.2
|
||||
|
||||
# 4.7.3 / 2021-02-04
|
||||
|
||||
- Refactored comments parsing to avoid Maximum function nesting level errors
|
||||
|
||||
# 4.7.2 / 2021-02-03
|
||||
|
||||
- Issue where Scenario Outline title was not populated into Examples
|
||||
- Updated translations from cucumber 16.0.0
|
||||
|
||||
# 4.7.1 / 2021-01-26
|
||||
|
||||
- Issue parsing comments before scenarios when following an Examples table
|
||||
|
||||
# 4.7.0 / 2021-01-24
|
||||
|
||||
- Provides better messages for TableNode construct errors
|
||||
- Now allows single character steps
|
||||
- Supports multiple Example Tables with tags
|
||||
|
||||
# 4.6.2 / 2020-03-17
|
||||
|
||||
- Fixed issues due to incorrect cache key
|
||||
|
||||
# 4.6.1 / 2020-02-27
|
||||
|
||||
- Fix AZ translations
|
||||
- Correctly filter features, now that the base path is correctly set
|
||||
|
||||
# 4.6.0 / 2019-01-16
|
||||
|
||||
- Updated translations (including 'Example' as synonym for 'Scenario' in `en`)
|
||||
|
||||
# 4.5.1 / 2017-08-30
|
||||
|
||||
- Fix regression in `PathsFilter`
|
||||
|
||||
# 4.5.0 / 2017-08-30
|
||||
|
||||
- Sync i18n with Cucumber Gherkin
|
||||
- Drop support for HHVM tests on Travis
|
||||
- Add `TableNode::fromList()` method (thanks @TravisCarden)
|
||||
- Add `ExampleNode::getOutlineTitle()` method (thanks @duxet)
|
||||
- Use realpath, so the feature receives the cwd prefixed (thanks @glennunipro)
|
||||
- Explicitly handle non-two-dimensional arrays in TableNode (thanks @TravisCarden)
|
||||
- Fix to line/linefilter scenario runs which take relative paths to files (thanks @generalconsensus)
|
||||
|
||||
# 4.4.5 / 2016-10-30
|
||||
|
||||
- Fix partial paths matching in `PathsFilter`
|
||||
|
||||
# 4.4.4 / 2016-09-18
|
||||
|
||||
- Provide clearer exception for non-writeable cache directories
|
||||
|
||||
# 4.4.3 / 2016-09-18
|
||||
|
||||
- Ensure we reset tags between features
|
||||
|
||||
# 4.4.2 / 2016-09-03
|
||||
|
||||
- Sync 18n with gherkin 3
|
||||
|
||||
# 4.4.1 / 2015-12-30
|
||||
|
||||
- Ensure keywords are trimmed when syncing translations
|
||||
- Sync 18n with cucumber
|
||||
|
||||
# 4.4.0 / 2015-09-19
|
||||
|
||||
- Added validation enforcing that all rows of a `TableNode` have the same number of columns
|
||||
- Added `TableNode::getColumn` to get a column from the table
|
||||
- Sync 18n with cucumber
|
||||
|
||||
# 4.3.0 / 2014-06-06
|
||||
|
||||
- Added `setFilters(array)` method to `Gherkin` class
|
||||
- Added `NarrativeFilter` for non-english `RoleFilter` lovers
|
||||
|
||||
# 4.2.1 / 2014-06-06
|
||||
|
||||
- Fix parsing of features without line feed at the end
|
||||
|
||||
# 4.2.0 / 2014-05-27
|
||||
|
||||
- Added `getKeyword()` and `getKeywordType()` methods to `StepNode`, deprecated `getType()`.
|
||||
Thanks to @kibao
|
||||
|
||||
# 4.1.3 / 2014-05-25
|
||||
|
||||
- Properly handle tables with rows terminating in whitespace
|
||||
|
||||
# 4.1.2 / 2014-05-14
|
||||
|
||||
- Handle case where Gherkin cache is broken
|
||||
|
||||
# 4.1.1 / 2014-05-05
|
||||
|
||||
- Fixed the compatibility with PHP 5.6-beta by avoiding to use the broken PHP array function
|
||||
- The YamlFileLoader no longer extend from ArrayLoader but from AbstractFileLoader
|
||||
|
||||
# 4.1.0 / 2014-04-20
|
||||
|
||||
- Fixed scenario tag filtering
|
||||
- Do not allow multiple multiline step arguments
|
||||
- Sync 18n with cucumber
|
||||
|
||||
# 4.0.0 / 2014-01-05
|
||||
|
||||
- Changed the behavior when no loader can be found for the resource. Instead of throwing an exception, the
|
||||
Gherkin class now returns an empty array.
|
||||
|
||||
# 3.1.3 / 2014-01-04
|
||||
|
||||
- Dropped the dependency on the Symfony Finder by using SPL iterators directly
|
||||
- Added testing on HHVM on Travis. HHVM is officially supported (previous release was actually already compatible)
|
||||
|
||||
# 3.1.2 / 2014-01-01
|
||||
|
||||
- All paths passed to PathsFilter are converted using realpath
|
||||
|
||||
# 3.1.1 / 2013-12-31
|
||||
|
||||
- Add `ComplexFilterInterace` that has complex behavior for scenarios and requires to pass
|
||||
feature too
|
||||
- `TagFilter` is an instance of a `ComplexFilterInterace` now
|
||||
|
||||
# 3.1.0 / 2013-12-31
|
||||
|
||||
- Example node is a scenario
|
||||
- Nodes do not have uprefs (memory usage fix)
|
||||
- Scenario filters do not depend on feature nodes
|
||||
|
||||
# 3.0.5 / 2014-01-01
|
||||
|
||||
- All paths passed to PathsFilter are converted using realpath
|
||||
|
||||
# 3.0.4 / 2013-12-31
|
||||
|
||||
- TableNode is now traversable using foreach
|
||||
- All possibly thrown exceptions implement Gherkin\Exception interface
|
||||
- Sync i18n with cucumber
|
||||
|
||||
# 3.0.3 / 2013-09-15
|
||||
|
||||
- Extend ExampleNode with additional methods
|
||||
|
||||
# 3.0.2 / 2013-09-14
|
||||
|
||||
- Extract `KeywordNodeInterface` and `ScenarioLikeInterface`
|
||||
- Add `getIndex()` methods to scenarios, outlines, steps and examples
|
||||
- Throw proper exception for fractured node tree
|
||||
|
||||
# 3.0.1 / 2013-09-14
|
||||
|
||||
- Use versioned subfolder in FileCache
|
||||
|
||||
# 3.0.0 / 2013-09-14
|
||||
|
||||
- A lot of optimizations in Parser and Lexer
|
||||
- Node tree is now immutable by nature (no setters)
|
||||
- Example nodes are now part of the node tree. They are lazily generated by Outline node
|
||||
- Sync with latest cucumber i18n
|
||||
|
||||
# 2.3.4 / 2013-08-11
|
||||
|
||||
- Fix leaks in memory cache
|
||||
|
||||
# 2.3.3 / 2013-08-11
|
||||
|
||||
- Fix encoding bug introduced with previous release
|
||||
- Sync i18n with cucumber
|
||||
|
||||
# 2.3.2 / 2013-08-11
|
||||
|
||||
- Explicitly use utf8 encoding
|
||||
|
||||
# 2.3.1 / 2013-08-10
|
||||
|
||||
- Support `an` prefix with RoleFilter
|
||||
|
||||
# 2.3.0 / 2013-08-04
|
||||
|
||||
- Add RoleFilter
|
||||
- Add PathsFilter
|
||||
- Add MemoryCache
|
||||
|
||||
# 2.2.9 / 2013-03-02
|
||||
|
||||
- Fix dependency version requirement
|
||||
|
||||
# 2.2.8 / 2013-03-02
|
||||
|
||||
- Features filtering behavior change. Now emptified (by filtering) features
|
||||
that do not match filter themselves are removed from resultset.
|
||||
- Small potential bug fix in TableNode
|
||||
|
||||
# 2.2.7 / 2013-01-27
|
||||
|
||||
- Fixed bug in i18n syncing script
|
||||
- Resynced Gherkin i18n
|
||||
|
||||
# 2.2.6 / 2013-01-26
|
||||
|
||||
- Support long row hashes in tables ([see](https://github.com/Behat/Gherkin/issues/40))
|
||||
- Synced Gherkin i18n
|
||||
|
||||
# 2.2.5 / 2012-09-26
|
||||
|
||||
- Fixed issue with loading empty features
|
||||
- Synced Gherkin i18n
|
||||
|
||||
# 2.2.4 / 2012-08-03
|
||||
|
||||
- Fixed exception message for "no loader found"
|
||||
|
||||
# 2.2.3 / 2012-08-03
|
||||
|
||||
- Fixed minor loader bug with empty base path
|
||||
- Synced Gherkin i18n
|
||||
|
||||
# 2.2.2 / 2012-07-01
|
||||
|
||||
- Added ability to filter outline scenarios by line and range filters
|
||||
- Synced Gherkin i18n
|
||||
- Refactored table parser to read row line numbers too
|
||||
|
||||
# 2.2.1 / 2012-05-04
|
||||
|
||||
- Fixed StepNode `getLanguage()` and `getFile()`
|
||||
|
||||
# 2.2.0 / 2012-05-03
|
||||
|
||||
- Features freeze after parsing
|
||||
- Implemented GherkinDumper (@Halleck45)
|
||||
- Synced i18n with Cucumber
|
||||
- Updated inline documentation
|
||||
|
||||
# 2.1.1 / 2012-03-09
|
||||
|
||||
- Fixed caching bug, where `isFresh()` always returned false
|
||||
|
||||
# 2.1.0 / 2012-03-09
|
||||
|
||||
- Added parser caching layer
|
||||
- Added support for table delimiter escaping (use `\|` for that)
|
||||
- Added LineRangeFilter (thanks @headrevision)
|
||||
- Synced i18n dictionary with cucumber/gherkin
|
||||
|
||||
# 2.0.2 / 2012-02-04
|
||||
|
||||
- Synced i18n dictionary with cucumber/gherkin
|
||||
|
||||
# 2.0.1 / 2012-01-26
|
||||
|
||||
- Fixed issue about parsing features without indentation
|
||||
|
||||
# 2.0.0 / 2012-01-19
|
||||
|
||||
- Background titles support
|
||||
- Correct parsing of titles/descriptions (hirarchy lexing)
|
||||
- Migration to the cucumber/gherkin i18n dictionary
|
||||
- Speed optimizations
|
||||
- Refactored KeywordsDumper
|
||||
- New loaders
|
||||
- Bugfixes
|
||||
|
||||
# 1.1.4 / 2012-01-08
|
||||
|
||||
- Read feature description even if it looks like a step
|
||||
|
||||
# 1.1.3 / 2011-12-14
|
||||
|
||||
- Removed file loading routines from Parser (fixes `is_file()` issue on some systems - thanks
|
||||
@flodocteurklein)
|
||||
|
||||
# 1.1.2 / 2011-12-01
|
||||
|
||||
- Updated spanish trasnaltion (@anbotero)
|
||||
- Integration with Composer and Travis CI
|
||||
|
||||
# 1.1.1 / 2011-07-29
|
||||
|
||||
- Updated pt language step types (@danielcsgomes)
|
||||
- Updated vendors
|
||||
|
||||
# 1.1.0 / 2011-07-16
|
||||
|
||||
- Return all tags, including inherited in `Scenario::getTags()`
|
||||
- New `Feature::getOwnTags()` and `Scenario::getOwnTags()` method added,
|
||||
which returns only own tags
|
||||
|
||||
# 1.0.8 / 2011-06-29
|
||||
|
||||
- Fixed comments parsing.
|
||||
You can’t have comments at the end of a line # like this
|
||||
# But you can still have comments at the beginning of a line
|
||||
|
||||
# 1.0.7 / 2011-06-28
|
||||
|
||||
- Added `getRaw()` method to PyStringNode
|
||||
- Updated vendors
|
||||
|
||||
# 1.0.6 / 2011-06-17
|
||||
|
||||
- Updated vendors
|
||||
|
||||
# 1.0.5 / 2011-06-10
|
||||
|
||||
- Fixed bug, introduced with 1.0.4 - hash in PyStrings
|
||||
|
||||
# 1.0.4 / 2011-06-10
|
||||
|
||||
- Fixed inability to comment pystrings
|
||||
|
||||
# 1.0.3 / 2011-04-21
|
||||
|
||||
- Fixed introduced with 1.0.2 pystring parsing bug
|
||||
|
||||
# 1.0.2 / 2011-04-18
|
||||
|
||||
- Fixed bugs in text with comments parsing
|
||||
|
||||
# 1.0.1 / 2011-04-01
|
||||
|
||||
- Updated vendors
|
||||
|
||||
# 1.0.0 / 2011-03-08
|
||||
|
||||
- Updated vendors
|
||||
|
||||
# 1.0.0RC2 / 2011-02-25
|
||||
|
||||
- Windows support
|
||||
- Missing phpunit config
|
||||
|
||||
# 1.0.0RC1 / 2011-02-15
|
||||
|
||||
- Huge optimizations to Lexer & Parser
|
||||
- Additional loaders (Yaml, Array, Directory)
|
||||
- Filters (Tag, Name, Line)
|
||||
- Code refactoring
|
||||
- Nodes optimizations
|
||||
- Additional tests for exceptions and translations
|
||||
- Keywords dumper
|
||||
|
||||
# 0.2.0 / 2011-01-05
|
||||
|
||||
- New Parser & Lexer (based on AST)
|
||||
- New verbose parsing exception handling
|
||||
- New translation mechanics
|
||||
- 47 brand new translations (see i18n)
|
||||
- Full test suite for everything from AST nodes to translations
|
||||
|
||||
[4.16.1]: https://github.com/Behat/Gherkin/compare/v4.16.0...v4.16.1
|
||||
[4.16.0]: https://github.com/Behat/Gherkin/compare/v4.15.0...v4.16.0
|
||||
[4.15.0]: https://github.com/Behat/Gherkin/compare/v4.14.0...v4.15.0
|
||||
[4.14.0]: https://github.com/Behat/Gherkin/compare/v4.13.0...v4.14.0
|
||||
[4.13.0]: https://github.com/Behat/Gherkin/compare/v4.12.0...v4.13.0
|
||||
[4.12.0]: https://github.com/Behat/Gherkin/compare/v4.11.0...v4.12.0
|
||||
[4.11.0]: https://github.com/Behat/Gherkin/compare/v4.10.0...v4.11.0
|
||||
22
vendor/behat/gherkin/LICENSE
vendored
Normal file
22
vendor/behat/gherkin/LICENSE
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
Copyright (c) 2011-2013 Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
81
vendor/behat/gherkin/README.md
vendored
Normal file
81
vendor/behat/gherkin/README.md
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
# Behat Gherkin Parser
|
||||
|
||||
This is the php Gherkin parser for Behat. It comes bundled with more than 40 native languages (see `i18n.php`) support
|
||||
and clean architecture.
|
||||
|
||||
## Useful Links
|
||||
|
||||
- [Behat Site](https://behat.org)
|
||||
- [Note on Patches/Pull Requests](CONTRIBUTING.md)
|
||||
|
||||
## Usage Example
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$keywords = new Behat\Gherkin\Keywords\ArrayKeywords(array(
|
||||
'en' => array(
|
||||
'feature' => 'Feature',
|
||||
'background' => 'Background',
|
||||
'scenario' => 'Scenario',
|
||||
'scenario_outline' => 'Scenario Outline|Scenario Template',
|
||||
'examples' => 'Examples|Scenarios',
|
||||
'given' => 'Given',
|
||||
'when' => 'When',
|
||||
'then' => 'Then',
|
||||
'and' => 'And',
|
||||
'but' => 'But'
|
||||
),
|
||||
'en-pirate' => array(
|
||||
'feature' => 'Ahoy matey!',
|
||||
'background' => 'Yo-ho-ho',
|
||||
'scenario' => 'Heave to',
|
||||
'scenario_outline' => 'Shiver me timbers',
|
||||
'examples' => 'Dead men tell no tales',
|
||||
'given' => 'Gangway!',
|
||||
'when' => 'Blimey!',
|
||||
'then' => 'Let go and haul',
|
||||
'and' => 'Aye',
|
||||
'but' => 'Avast!'
|
||||
)
|
||||
));
|
||||
$lexer = new Behat\Gherkin\Lexer($keywords);
|
||||
$parser = new Behat\Gherkin\Parser($lexer);
|
||||
|
||||
$feature = $parser->parse(file_get_contents('some.feature'));
|
||||
```
|
||||
|
||||
## Installing Dependencies
|
||||
|
||||
```shell
|
||||
curl https://getcomposer.org/installer | php
|
||||
php composer.phar update
|
||||
```
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
- Konstantin Kudryashov [everzet](https://github.com/everzet) [original developer]
|
||||
- Andrew Coulton [acoulton](https://github.com/acoulton) [current maintainer]
|
||||
- Carlos Granados [carlos-granados](https://github.com/carlos-granados) [current maintainer]
|
||||
- Christophe Coevoet [stof](https://github.com/stof) [current maintainer]
|
||||
- Other [awesome developers](https://github.com/Behat/Gherkin/graphs/contributors)
|
||||
|
||||
Support the project
|
||||
-------------------
|
||||
|
||||
Behat is free software, maintained by volunteers as a gift for users. If you'd like to see
|
||||
the project continue to thrive, and particularly if you use it for work, we'd encourage you
|
||||
to contribute.
|
||||
|
||||
Contributions of time - whether code, documentation, or support reviewing PRs and triaging
|
||||
issues - are very welcome and valued by the maintainers and the wider Behat community.
|
||||
|
||||
But we also believe that [financial sponsorship is an important part of a healthy Open Source
|
||||
ecosystem](https://opensourcepledge.com/about/). Maintaining a project like Behat requires a
|
||||
significant commitment from the core team: your support will help us to keep making that time
|
||||
available over the long term. Even small contributions make a big difference.
|
||||
|
||||
You can support [@acoulton](https://github.com/acoulton), [@carlos-granados](https://github.com/carlos-granados) and
|
||||
[@stof](https://github.com/stof) on GitHub sponsors. If you'd like to discuss supporting us in a different way, please
|
||||
get in touch!
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user