Yii 框架的实时新闻 关于 Yii 框架的新闻、最新扩展和维基文章。 2024 年 9 月 19 日星期四 12:05:58 +0000 Zend_Feed_Writer 2 (http://framework.zend.com) https://yiiframework.cn/ [扩展] duna/qrcode 2024 年 9 月 19 日星期四 12:05:57 +0000 https://yiiframework.cn/extension/duna/qrcodehttps://yiiframework.cn/extension/duna/qrcode davidsgallan davidsgallan

Duna 二维码库

  1. 概述
  2. 安装
  3. 使用
  4. 许可证
  5. 贡献
  6. 问题和支持

一个通用的二维码生成库,支持 HTML、PNG 和 SVG 输出格式。

GitHub repo stars

概述

Duna 二维码库是一个二维码生成工具,最初基于 Duna v8.0 之前的捆绑的 QrCode 库,由 Laurent Minguet 开发。它在 LGPL 许可下发布,为二维码生成提供灵活且开源的解决方案。

安装

要安装该库,请使用 Composer

$ composer require duna/qrcode

使用

以下是使用 Duna 二维码库的快速指南

生成二维码

首先,包含必要的类并创建一个二维码实例

<?php

use Duna\Helpers\QrCode\QrCode;
use Duna\Helpers\QrCode\Output;

$qrCode = new QrCode('Lorem ipsum dolor sit amet');
输出格式
PNG 输出

要生成二维码的 PNG 图片,指定尺寸和颜色,请使用

// Create PNG output
$output = new Output\Png();

// Generate PNG data with a specified width, background color (white), and foreground color (black)
$data = $output->output($qrCode, 100, [255, 255, 255], [0, 0, 0]);

// Save the PNG data to a file
file_put_contents('file.png', $data);
SVG 输出

对于 SVG 输出,它对于可缩放矢量图形很有用

// Create SVG output
$output = new Output\Svg();

// Generate SVG data with a specified width, background color (white), and foreground color (black)
echo $output->output($qrCode, 100, 'white', 'black');
HTML 输出

要将二维码显示为 HTML 表格

// Create HTML output
$output = new Output\Html();

// Generate HTML table representation of the QR code
echo $output->output($qrCode);

许可证

该库在 GNU Lesser General Public License (LGPL) v3.0 下提供。有关详细信息,请参阅 LICENSE 文件。

贡献

欢迎贡献!有关更多信息,请参阅我们的 CONTRIBUTING 指南。

问题和支持

对于问题和支持,请参阅我们的 问题跟踪器 或联系社区。

]]>
0
[新闻] Yii HTML 3.7 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/663/yii-html-3-7https://yiiframework.cn/news/663/yii-html-3-7 vjik vjik

Yii HTML 包的 3.7 版已发布。有一些改进

  • 添加了 Script::nonce()Script::getNonce() 方法用于 CSP;
  • 将支持的枚举值添加到 Select 标签。
]]>
0
[新闻] Yii Hydrator 1.5 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/662/yii-hydrator-1-5https://yiiframework.cn/news/662/yii-hydrator-1-5 vjik vjik

Yii Hydrator 包的 1.5 版已发布。以下是新版本中包含的改进列表

  • 添加了 EnumTypeCaster,它将值转换为枚举;
  • 修复了从父类填充只读属性。
]]>
0
[新闻] Yii Validator 2.1 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/661/yii-validator-2-1https://yiiframework.cn/news/661/yii-validator-2-1 vjik vjik

Yii Validator 包的 2.1 版已发布。以下是新版本中包含的更改列表

  • 将 PHP 属性中的规则与通过 getRules() 方法提供的规则合并;
  • Ip 规则中使用 Yiisoft\NetworkUtilities\IpRanges:添加 getIpRanges() 方法并弃用 getRanges()getNetworks()isAllowed() 方法;
  • IpHandler 中使用 NEGATION_CHARACTER 常量,而不是声明自己的常量。
]]>
0
[维基] 在所有位于相同域/子域下的 Yii2 应用程序/存储库中使用单一登录会话 2024 年 9 月 10 日星期二 12:26:07 +0000 https://yiiframework.cn/wiki/2580/use-single-login-session-on-all-your-yii2-applicationrepository-under-same-domainsub-domainhttps://yiiframework.cn/wiki/2580/use-single-login-session-on-all-your-yii2-applicationrepository-under-same-domainsub-domain aayushmhu aayushmhu

有许多博客展示了如何为 Yii2 应用程序使用单独的登录,但在本文中,我将向您展示如何为所有 Yii2 高级版、Yii2 基础版、应用程序使用单个登录屏幕。它还将在您的域位于不同的服务器或同一服务器时起作用。

以下是一些实现此目的需要遵循的步骤。

1. 对于高级模板

步骤 1:将其添加到您在

/path/common/config/main.php 中的组件中

  'components' => [
        'user' => [
            'identityClass' => 'common\models\User',
            'enableAutoLogin' => true,
            'identityCookie' => ['name' => '_identity', 'httpOnly' => true],
        ],
        'request' => [
            'csrfParam' => '_csrf',
        ],
    ],

步骤 2:将 Session 和 Request 添加到 main-local.php

/path/common/config/main-local.php

   'components' => [
        'session' => [
            'cookieParams' => [
                'path' => '/',
                'domain' => ".example.com",
            ],
        ],
        'user' => [
            'identityCookie' => [
                'name' => '_identity',
                'path' => '/',
                'domain' => ".example.com",
            ],
        ],
        'request' => [
            'csrfCookie' => [
                'name' => '_csrf',
                'path' => '/',
                'domain' => ".example.com",
            ],
        ],
    ],

注意:example.com 是主域。所有其他域都应为此域的子域。

步骤 3:现在为所有应用程序更新相同的验证密钥

/path/frontend/config/main-local.php

/path/backend/config/main-local.php

 'components' => [
        'request' => [
            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
            'cookieValidationKey' => 'fFUeb5HDj2P-1a1FTIqya8qOE',
        ],
    ],

注意:从您前端和后端应用程序的 main.php 中删除 Session 和 request 密钥。

步骤 4:请注意,您也有一个控制台应用程序,因此在您的控制台应用程序的 main-local.php 中更新 session、user 和 request

/path/console/config/main-local.php

 'components' => [
        'session' => null,
        'user' => null,
        'request' => null,
    ]

2. 对于基础模板

此外,如果您为另一个项目安装了基础模板,并且您希望为此模板使用相同的登录。要实现此目的,请遵循以下步骤

步骤 1:更新您基础模板的 main-local.php

/path/basic-app/config/main-local.php


 'components' => [
        'session' => [
            'cookieParams' => [
                'path' => '/',
                'domain' => ".example.com",
            ],
        ],
        'user' => [
            'identityCookie' => [
                'name' => '_identity',
                'path' => '/',
                'domain' => ".example.com",
            ],
        ],
        'request' => [
            'csrfCookie' => [
                'name' => '_csrf',
                'path' => '/',
                'domain' => ".example.com",
            ],
        ],

    ],

我希望您了解如何为所有域和子域或存储库使用单一登录。

:) 感谢阅读

]]>
0
[新闻] Yii Swagger 2.1 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/660/yii-swagger-2-1https://yiiframework.cn/news/660/yii-swagger-2-1 arogachev arogachev

Yii Swagger 包的 2.1 版已发布。以下是新版本中包含的更改列表

  • 添加了对 psr/http-message 版本 ^2.0 的支持。
  • yiisoft/yii-view 版本的最低要求提升到 ^7.1
  • 添加了 \Yiisoft\Swagger\Action\SwaggerJson\Yiisoft\Swagger\Action\SwaggerUi 操作,将 \Yiisoft\Swagger\Middleware\SwaggerJson\Yiisoft\Swagger\Middleware\SwaggerUi 类标记为已弃用(它们将在下一个主要版本中删除)。
  • 添加了对版本 5 的 swagger-api/swagger-ui 的支持。
]]>
0
[新闻] Yii 网络工具 1.2 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/659/yii-network-utilities-1-2https://yiiframework.cn/news/659/yii-network-utilities-1-2 arogachev arogachev

Yii 网络工具 包的 1.2 版已发布。以下是新版本中包含的更改列表

  • 添加了 IP_PATTERNIP_REGEXP 常量到 IpHelper 以检查 IPv4 和 IPv6 版本的 IP。
  • 添加了 NEGATION_CHARACTER 常量到用于否定范围的 IpRanges
  • 添加了 isIpv4()isIpv6()isIp() 方法到 IpHelper
]]>
0
[新闻] Yii 表单模型 1.0 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/658/yii-form-model-1-0https://yiiframework.cn/news/658/yii-form-model-1-0 vjik vjik

Yii 表单模型 包的第一个版本已经发布。它为表单模型提供基础,并有助于填写、验证和显示它们。

要使用,请定义一个表单模型

use Yiisoft\FormModel\Attribute\Safe;
use Yiisoft\FormModel\FormModel;
use Yiisoft\Validator\Rule\Email;
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Required;

final class LoginForm extends FormModel
{
    #[Label('Your login')]
    #[Required]
    #[Length(min: 4, max: 40, skipOnEmpty: true)]
    #[Email(skipOnEmpty: true)]
    private ?string $login = null;

    #[Label('Your password')]
    #[Required]
    #[Length(min: 8, skipOnEmpty: true)]
    private ?string $password = null;

    #[Label('Remember me for 1 week')]
    #[Safe]
    private bool $rememberMe = false;
}

用数据填充它并使用表单提取器进行验证

use Psr\Http\Message\RequestInterface;
use Yiisoft\FormModel\FormHydrator;
use Yiisoft\FormModel\FormModel;

final class AuthController 
{
    public function login(RequestInterface $request, FormHydrator $formHydrator): ResponseInterface
    {
        $formModel = new LoginForm();
        $errors = [];
        if ($formHydrator->populateFromPostAndValidate($formModel, $request)) {
            $errors = $formModel->getValidationResult()->getErrorMessagesIndexedByProperty();
        }
        
        // You can pass $formModel and $errors to the view now.
    }
}

使用视图中的字段显示它

use Yiisoft\FormModel\Field;
use Yiisoft\FormModel\FormModel;

echo Field::text($formModel, 'login');
echo Field::password($formModel, 'password');
echo Field::checkbox($formModel, 'rememberMe');

// ...
]]>
0
[新闻] Yii 表单 1.0 2024 年 8 月 26 日星期一 19:05:36 +0000 https://yiiframework.cn/news/657/yii-form-1-0https://yiiframework.cn/news/657/yii-form-1-0 vjik vjik

Yii 表单 包的第一个版本已经发布。它提供了一组小部件,有助于动态生成 HTML 表单的服务器端。以下小部件开箱即用

  • 输入字段:CheckboxCheckboxListDateDateTimeLocalEmailFileHiddenImageNumberPasswordRadioListRangeSelectTelephoneTextTextareaTimeUrl
  • 按钮:ButtonResetButtonSubmitButton
  • 组小部件:ButtonGroupFieldset
  • 其他:ErrorSummary

一般用法

use Yiisoft\Form\PureField\Field;

echo Field::text('firstName', theme: 'horizontal')
  ->label('First Name')
  ->autofocus();
echo Field::text('lastName', theme: 'horizontal')
  ->label('Last Name');
echo Field::select('sex')
  ->label('Sex')
  ->optionsData(['m' => 'Male', 'f' => 'Female'])
  ->prompt('—');
echo Field::number('age')
  ->label('Age')
  ->hint('Please enter your age.');
echo Field::submitButton('Submit')
  ->buttonClass('primary');
]]>
0
[新闻] Yii Hydrator 1.4 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/656/yii-hydrator-1-4https://yiiframework.cn/news/656/yii-hydrator-1-4 vjik vjik

Yii Hydrator 包的 1.4 版已发布。以下是新版本中包含的改进列表

  • 添加了 ToArrayOfStrings 参数属性;
  • 将支持的枚举值添加到 Collection
]]>
0
[新闻] Yii HTML 3.6 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/655/yii-html-3-6https://yiiframework.cn/news/655/yii-html-3-6 vjik vjik

Yii HTML 包的 3.6 版已发布。有一些改进和修复

  • 当属性名称为空或包含禁止符号时,在 Html::renderAttribute() 中抛出 InvalidArgumentException
  • Stringable 和数组值支持添加到文本区域标签。
  • 将支持的枚举值添加到 CheckboxListRadioList 小部件。
  • 修复 Html::renderTagAttributes()null 值属性的输出。
]]>
0
[新闻] Yii Auth JWT 2.1 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/654/yii-auth-jwt-2-1https://yiiframework.cn/news/654/yii-auth-jwt-2-1 arogachev arogachev

Yii Auth JWT 包的 2.1 版已发布。以下是新版本中包含的改进列表

  • 将依赖项中的多个 web-token/* 包替换为一个 web-token/jwt-library,将 PHP 的最低版本更新到 8.1。
  • 添加了对版本 ^2.0psr/http-message 的支持。
]]>
0
[新闻] Yii Hydrator 1.3 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/653/yii-hydrator-1-3https://yiiframework.cn/news/653/yii-hydrator-1-3 arogachev arogachev

Yii Hydrator 包的 1.3 版已发布。以下是新版本中包含的改进列表

  • 通过 Collection PHP 属性添加了对集合的支持;
  • 添加了提取器依赖项和 withHydrator() 方法到 ParameterAttributesHandler
  • 添加了提取器依赖项和 getHydrator() 方法到 ParameterAttributeResolveContext
  • 允许提取未初始化的 readonly 属性。
]]>
0
[新闻] Yii 网络工具 1.1 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/652/yii-network-utilities-1-1https://yiiframework.cn/news/652/yii-network-utilities-1-1 vjik vjik

Yii 网络工具 包的 1.1 版已发布。有一些改进

  • 添加了 IpRanges,它表示一组允许或禁止的 IP 范围;
  • 修复了在 PHP 8.0+ 中将 IP 地址转换为位表示形式时发生的错误。
]]>
0
[新闻] Yii Validator 2.0 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/651/yii-validator-2-0https://yiiframework.cn/news/651/yii-validator-2-0 vjik vjik

Yii Validator 的主要版本已标记。

  • 添加了 Each::PARAMETER_EACH_KEY 验证上下文参数,它在 Each 规则处理期间可用,并包含当前密钥
  • 在错误消息中包含属性名称(如果存在)
  • 添加 PHP 属性,用于设置属性标签,以在错误消息中使用
  • 添加 InEnum 规则
  • 将类/特征/方法/变量/占位符名称中的 "attribute" 更改为 "property"
  • Error::getValuePath()$escape 参数的类型从 bool|string|null 更改为 string|null
  • OneOfAtLeast 规则的错误消息中列出翻译后的属性
  • 修复 OneOf 规则中错误消息的含义
  • 改进 OneOfAtLeast 规则中错误消息的含义,并使用复数形式
  • 禁止 $min 大于 AtLeast 配置中 $attributes 的数量
  • getName() 方法从 RuleInterface 移动到 RuleWithOptionsInterface
  • RuleWithOptionsInterface 重命名为 DumpedRuleInterface
  • 在使用 RulesDumper 导出时,使用 FQCN 作为内置规则的名称
  • 在使用 RulesDumper 导出时,使用 FQCN 作为未实现 DumpedRuleInterface 的规则的名称
  • 将规则构造函数中 $skipOnEmpty 参数的类型从 mixed 更改为 bool|callable|null
  • RuleHandlerInterface::validate()$rule 参数的类型从 object 更改为 RuleInterface
  • AtLeast 规则重命名为 FilledAtLeast,将 OneOf 规则重命名为 FilledOnlyOneOf
  • 向与不正确输入相关的错误消息添加类型
  • 如果代码使用 PHP 8.3 运行,则在 JsonHandler 中使用内置的 PHP 函数 json_validate()
  • 改进 Result 类中的 psalm 注释
  • 添加德语翻译
  • 使用 PHP 8.3 以下版本中的内置 PHP 函数,简化 JsonHandler 中 JSON 的验证
  • 将 PHP 的最低版本提升到 8.1
  • 重构 Result::add():将 array_merge()foreach 中移除
  • 使 RulesNormalizer::normalize() 中的参数 $rules 可选
  • 使Json::$message 更清晰
  • 修复Nested 规则中错误消息中属性名称的使用
  • 数据对象 PHP 属性提供的规则未在RulesNormalizer::normalize() 中使用
  • 修复Each::$incorrectInputKeyMessagetype 参数的错误值

查看 升级说明,其中包含有关将应用程序中的软件包升级到此主要版本的说明。

]]>
0
[新闻] Yii 2.0.51 Thu, 18 Jul 2024 20:06:33 +0000 https://yiiframework.cn/news/650/yii-2-0-51https://yiiframework.cn/news/650/yii-2-0-51 samdark samdark

我们很高兴宣布 Yii 框架版本 2.0.51 发布。

请参考 https://yiiframework.cn/download/ 中的说明安装或升级到此版本。

此版本修复了 2.0.50 中的回归,错误处理程序与 PHP 8.3 的兼容性以及一些错误。

感谢所有为框架做出贡献的 Yii 社区成员、保持文档翻译最新的翻译者以及在论坛上回答问题的社区成员。

有很多活跃的 Yii 社区,因此如果您需要帮助或想分享您的经验,请随时加入它们。

可以在 CHANGELOG 中找到完整的更改列表。

]]>
0
[扩展] pingcrm-yii2-vue3 Wed, 17 Jul 2024 09:28:16 +0000 https://yiiframework.cn/extension/pingcrm-yii2-vue3https://yiiframework.cn/extension/pingcrm-yii2-vue3 toatall toatall

Yii 2 上的 Ping CRM

  1. 演示
  2. 安装
  3. 运行测试
  4. 要求
  5. 扩展此项目
  6. 鸣谢

一个 Yii 2 演示应用程序,用于说明 Inertia.js 的工作原理。

使用 Inertia,您可以使用经典的服务器端路由和控制器构建单页应用程序,而无需构建 API。

此应用程序是原始 用 Laravel 编写的 Ping CRM 的移植版本,并基于 Yii 2 基本项目模板

screenshot.png

基于 Yii 2 上的 Ping CRM 应用程序 githubyii 扩展

更改:将 Vue 更新到版本 3,更新了 npm 包和 composer。将 Vue 文件转换为 Composition API(脚本设置)。

演示

https://pingcrm-yii2.tebe.ch

安装

在本地克隆仓库

git clone https://github.com/toatall/pingcrm-yii2-vue3 pingcrm-yii2-vue3
cd pingcrm-yii2-vue3

安装 PHP 依赖项

composer install

安装 NPM 依赖项

npm ci

构建资产

npm run css-dev
npm run dev

创建 SQLite 数据库。您也可以使用其他数据库(MySQL、Postgres),只需相应地更新您的配置。

touch database/database.sqlite

运行数据库迁移

php yii migrate

运行数据库播种器

php yii db/seed

运行开发服务器(输出将给出地址)

php yii serve

您已准备好!在浏览器中访问 Ping CRM,并使用以下用户名和密码登录

  • 用户名:johndoe@example.com
  • 密码:secret

运行测试

要运行 Ping CRM 测试,请运行

(to be done)

要求

  • PHP >=7.4.0
  • Node.js & NPM
  • SQLite

扩展此项目

在使用新功能扩展此项目时,需要执行以下步骤。

在后端
  • 添加新的控制器,该控制器继承自 inertia 控制器
  • 添加一个或多个操作
  • 从操作中返回,调用 inertia 渲染方法
<?php

namespace app\controllers;

use tebe\inertia\web\Controller;

class CustomController extends Controller
{
    public function actionIndex()
    {
        $params = [
            'data' => [],
        ];
        return $this->inertia('demo/index', $params);
    }
}

您可以在 https://github.com/tbreuss/yii2-inertia 上找到更多信息。

在前端
  • resources/js/Pages 下为在后端添加的每个控制器操作添加一个新页面
  • 复制粘贴现有的页面示例之一
  • 根据需要实现和/或扩展 Vue.js 内容
  • 使用这里和 package.json 中描述的前端工具

您可以在 https://inertia.laravel.net.cn 上找到更多信息。

鸣谢

  • Jonathan Reinink (@reinink) 和贡献者的原始作品
  • 移植到 Yii 2 的 Thomas Breuss (@tbreuss)
  • 由 @toatall (https://github.com/toatall) 修改
]]>
0
[新闻] Yii HTTP Runner 3.0 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/649/yii-http-runner-3-0https://yiiframework.cn/news/649/yii-http-runner-3-0 vjik vjik

Yii HTTP Runner 的主要版本已标记。在此版本中进行了多项更改。

  • 添加了更改缓冲区大小以发送消息体内容的功能。
  • 允许使用任何 PSR 日志记录器,默认情况下为NullLogger
  • 删除了ServerRequestFactory
  • SapiEmitter 标记为内部。
  • 修复了对未关闭其自身输出缓冲区的响应的错误处理。
]]>
0
[新闻] Yii 错误处理程序 3.3 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/648/yii-error-handler-3-3https://yiiframework.cn/news/648/yii-error-handler-3-3 vjik vjik

Yii 错误处理程序 包已更新,并包含以下增强功能

  • 添加复制 cURL 按钮,对请求标头进行排序,修复 UI;
  • 简化错误日志;
  • 通过单击显示完整参数;
  • 删除@anonymous 后缀;
  • 通过单击显示参数表;
  • 在文本选择时停止单击事件;
  • 在所有关闭函数(即使是延迟的函数)之后执行exit(1)
]]>
0
[新闻] Yii HTML 3.5 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/647/yii-html-3-5https://yiiframework.cn/news/647/yii-html-3-5 vjik vjik

Yii HTML 包的 3.5 版本发布。有一些改进

  • hr 标签添加了类和Html::hr() 方法;
  • 添加了对aria-describedby 属性中多个元素的支持。
]]>
0
[新闻] Yii 日志记录库 2.1 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/news/646/yii-logging-library-2-1https://yiiframework.cn/news/646/yii-logging-library-2-1 vjik vjik

Yii 日志记录库 包已更新,并包含以下增强功能和新功能

  • 添加了新的静态方法Logger::assertLevelIsValid()Logger::assertLevelIsString()Logger::assertLevelIsSupported()
  • 添加了对消息模板变量中嵌套值的支持,例如{foo.bar}
  • 添加了上下文提供者;
  • 添加了DateTimeDateTimeImmutable 支持作为日志上下文中的时间;
  • 添加了Message::category() 方法和Message::DEFAULT_CATEGORY 常量,弃用CategoryFilter::DEFAULT 以支持它;
  • 添加了Message::trace() 方法;
  • 添加了Message::time() 方法;
  • 弃用了Logger::validateLevel() 方法;
  • 弃用了Logger 方法setTraceLevel()setExcludedTracePaths(),以支持上下文提供者的使用;
  • 弃用了Target 类中的setCommonContext()getCommonContext() 方法;
  • gettype() 替换为get_debug_type() 以生成异常消息;
  • Message 构造函数中$level 参数的类型更改为string
  • 修复了解析消息时出现的错误,这些消息包含无法转换为字符串的变量;
  • 修复了格式化跟踪时出现的错误,如果跟踪不包含“文件”和“行”。
]]>
0
[新闻] Yii 邮件发送器库 5.1 Tue, 02 Jul 2024 14:43:55 +0000 https://yiiframework.cn/news/645/yii-mailer-library-5-1https://yiiframework.cn/news/645/yii-mailer-library-5-1 vjik vjik

Yii 邮件发送器库 的次要版本已标记。有一些改进和修复

  • 允许在MessageFactory 中设置默认“from”值;
  • 将最小 PHP 版本提升到^8.1
  • 将必需的yiisoft/view 版本提升到^10.0
]]>
0
[新闻] Yii 视图渲染器 7.1 Mon, 01 Jul 2024 12:00:35 +0000 https://yiiframework.cn/news/644/yii-view-renderer-7-1https://yiiframework.cn/news/644/yii-view-renderer-7-1 vjik vjik

Yii 视图渲染器 的次要版本已标记。

  • 将必需的yiisoft/view 版本提升到^10.0
]]>
0
[扩展] sjaakp/yii2-random-provider 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/sjaakp/yii2-random-providerhttps://yiiframework.cn/extension/sjaakp/yii2-random-provider sjaakp sjaakp

yii2-random-provider

  1. 安装
  2. 使用 RandomProvider
带有随机选择的 ActiveDataProvider

Latest Stable Version Total Downloads License

RandomProvider 继承自 ActiveDataProvider,它是 Yii 2.0 PHP 框架的一部分。它以随机方式选择记录,在某些情况下,这可能比常规ActiveDataProvider(通常)的有序方式更具吸引力。RandomProvider 旨在与我的 LoadMorePager 协同工作,但它也可以与 LinkPager 或其他分页器一起使用。

请注意,RandomProvider 不支持CUBRIDdblib 数据库驱动程序。此外,我只在mysql 中对其进行了测试。我相信它可以与其他驱动程序一起使用。如果您有任何经验可以分享,我将不胜感激。

另外请注意,RandomProvider 使用一种名为“Order By Rand()”的算法。这很慢,而且扩展性不强。因此,建议仅在数据量相对较小的数据集(少于几千条记录)中使用RandomProvider。更多信息 这里

RandomProvider 的演示 这里

安装

以通常的方式使用 Composer 安装 yii2-random-provider。在您的composer.json 文件的require 部分添加以下内容

"sjaakp/yii2-random-provider": "*"

或运行

composer require sjaakp/yii2-random-provider

您可以通过 下载 ZIP 格式的源代码 手动安装 yii2-random-provider

使用 RandomProvider

RandomProvider 是 Yii 的 ActiveDataProvider 的直接替换。只需像使用ActiveDataProvider 一样使用它。

]]>
0
[扩展] yagas/yii2-debug4mongo 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/yagas/yii2-debug4mongohttps://yiiframework.cn/extension/yagas/yii2-debug4mongo yagas yagas

993323

Yii 2 调试用于 MongoDB

  1. 目录结构
  2. 安装依赖
  3. 安装说明
  4. 配置说明

本项目为yii2-debug的扩展,使用MongoDB对debug数据进行存储。

目录结构

  src/                 代码目录
  src/models/          数据模型
  src/views/           视图文件
  src/controllers/     控制器

安装依赖

  • PHP 支持>=5.4
  • yii2-mongodb
  • yii2-debug 支持>=2.1.25(基于此版本构建而来)

安装说明

composer require yagas/yii2-debug4mongo

配置说明

if (YII_ENV_DEV) {
    $config['bootstrap'][] = 'debug';
    $config['modules']['debug'] = [
        'class' => 'yagas\debug\Module',
        'logTarget' => [
            'class' => 'yagas\debug\LogTarget',
            'app_no' => 'localhost_001', // 为当前站点设定标识
        ],
        'percent' => 10, // 百分之十的几率清除历史数据(GC)
    ];
}
]]>
0
[扩展] diecoding/yii2-pdfjs Mon, 20 May 2024 17:11:05 +0000 https://yiiframework.cn/extension/diecoding/yii2-pdfjshttps://yiiframework.cn/extension/diecoding/yii2-pdfjs die-coding die-coding

Yii2 PDF.js

  1. 目录
  2. 安装
  3. 依赖项
  4. 使用

使用 PDF.js 预览 PDF 文件,用于 Yii2

Latest Stable Version Total Downloads Latest Stable Release Date Quality Score Build Status License PHP Version Require

Yii2 PDF.js 使用 PDF.js
演示:https://mozilla.github.io/pdf.js/web/viewer.html

目录

安装

包可在 Packagist 上获取,您可以使用 Composer 安装它。

composer require diecoding/yii2-pdfjs '^1.0'

或添加到您的composer.json 文件的 require 部分。

'diecoding/yii2-pdfjs': '^1.0'

依赖项

使用

设置模块
...
'modules'=>[
  'pdfjs' => [
       'class' => \diecoding\pdfjs\Module::class,
   ],
],
...

视图
基本用法
echo \diecoding\pdfjs\PdfJs::widget([
    'url' => '@web/uploads/dummy.pdf',
]);
带完整工具栏的直接 URL
echo Url::to(["/pdfjs", 'file' => Url::to('@web/uploads/dummy.pdf', true)], true);
自定义属性
echo \diecoding\pdfjs\PdfJs::widget([
    'url' => '@web/uploads/dummy.pdf',
    'options' => [
        'style' => [
            'width' => '100%',
            'height' => '500px',
        ],
    ],
]);
禁用工具栏部分
echo \diecoding\pdfjs\PdfJs::widget([
    'url' => '@web/uploads/dummy.pdf',
    'sections' => [
        'toolbarContainer' => false,
    ],
]);
]]>
0
[extension] xiaosongshu/rabbitmq 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/xiaosongshu/rabbitmqhttps://yiiframework.cn/extension/xiaosongshu/rabbitmq 2723659854 2723659854

rabbitmq 队列消息队列

项目地址:https://github.com/2723659854/rabbitmq

项目介绍

消息队列主要用于业务解耦,本项目采用rabbitmq,支持thinkPHP,laravel,webman,yii等常用框架,也可以单独使用。

安装方法 install
composer require xiaosongshu/rabbitmq
示例 demo
定义一个队列 queue
<?php
namespace app\commands;

require_once __DIR__.'/vendor/autoload.php';

class Demo extends \Xiaosongshu\Rabbitmq\Client
{

    /** 以下是rabbitmq配置 ,请填写您自己的配置 */
    /** @var string $host 服务器地址 */
    public static $host = "127.0.0.1";

    /** @var int $port 服务器端口 */
    public static $port = 5672;

    /** @var string $user 服务器登陆用户 */
    public static $user = "guest";

    /** @var string $pass 服务器登陆密码 */
    public static $pass = "guest";

    /**
     * 业务处理
     * @param array $params
     * @return int
     */
    public static function handle(array $params): int
    {
        //TODO 这里写你的业务逻辑
        // ...
        var_dump($params);
        return self::ACK;
        //return self::NACK;
    }
}

投递消息 publish
\app\commands\Demo::publish(['name'=>'tome','age'=>15]);

你可以在任何地方投递消息。

开启消费
\app\commands\Demo::consume();

你可以把消费者放到command命令行里面,使用命令行执行队列消费。举个例子(这里以yii为例子,你也可以换成laravel,webman,thinkPHP等其他框架): `php <?php

namespace app\commands;

use yii\console\Controller;

/**

  • @purpose 开启队列消费
  • @note 我只是一个例子 */ class QueueController extends Controller {

    /**

    • @api php yii queue/index
    • @return void
    • @throws \Exception
    • @comment 开启消费者 */ public function actionIndex() { Demo::consume(); } }
      开启消费者命令 consume
      ```bash
      php yii queue/index
      

      注:如果你需要开启多个消费者,那么可以在多个窗口执行开启消费者命令即可。当然你也可以使用多进程来处理。

      关闭消费者
\app\commands\Demo::close();
异常 Exception

队列使用过程中请使用 \RuntimeException和\Exception捕获异常

若需要使用延迟队列,那么rabbitmq服务需要安装延迟插件,否则会报错
测试

本项目根目录有一个demo.php的测试文件,可以复制到你的项目根目录,在命令行窗口直接在命令行执行以下命令即可。 `php php demo.php 测试文件代码如下:php <?php

namespace xiaosongshu\test; require_once DIR . '/vendor/autoload.php';

/**

  • demo
  • @purpose 定义一个队列演示 */ class Demo extends \Xiaosongshu\Rabbitmq\Client {

    /* 以下是rabbitmq配置 ,请填写您自己的配置 / /* @var string $host 服务器地址 / public static $host = "127.0.0.1";

    /* @var int $port 服务器端口 / public static $port = 5672;

    /* @var string $user 服务器登陆用户 / public static $user = "guest";

    /* @var string $pass 服务器登陆密码 / public static $pass = "guest";

    /**

    • 业务处理
    • @param array $params
    • @return int */ public static function handle(array $params): int { //TODO 这里写你的业务逻辑 // ... var_dump($params); /* 成功,返回ack / return self::ACK; /* 失败,返回NACK/ //return self::NACK; } }

/ 投递普通消息 */ \xiaosongshu\test\Demo::publish(['name' => 'tom']); \xiaosongshu\test\Demo::publish(['name' => 'jim']); \xiaosongshu\test\Demo::publish(['name' => 'jack']); /* 开启消费,本函数为阻塞,后面的代码不会执行 / \xiaosongshu\test\Demo::consume(); / 关闭消费者 */ \xiaosongshu\test\Demo::close(); `

联系作者:2723659854@qq.com ,你也可以直接提issues
]]>
0
[extension] xiaosongshu/yii2-rabbitmq Wed, 24 Apr 2024 09:33:36 +0000 https://yiiframework.cn/extension/xiaosongshu/yii2-rabbitmqhttps://yiiframework.cn/extension/xiaosongshu/yii2-rabbitmq 2723659854 2723659854

rabbitmq 队列 延迟队列

安装方法 install

composer require xiaosongshu/yii2-rabbitmq
示例 demo
定义一个队列 queue
<?php
namespace app\commands;

require_once __DIR__.'/vendor/autoload.php';

class Demo extends \Xiaosongshu\Rabbitmq\Client
{

    /** 以下是rabbitmq配置 ,请填写您自己的配置 */
    /** @var string $host 服务器地址 */
    public static $host = "127.0.0.1";

    /** @var int $port 服务器端口 */
    public static $port = 5672;

    /** @var string $user 服务器登陆用户 */
    public static $user = "guest";

    /** @var string $pass 服务器登陆密码 */
    public static $pass = "guest";

    /**
     * 业务处理
     * @param array $params
     * @return int
     */
    public static function handle(array $params): int
    {
        //TODO 这里写你的业务逻辑
        // ...
        var_dump($params);
        return self::ACK;
        //return self::NACK;
    }
}

投递消息 publish
\app\commands\Demo::publish(['name'=>'tome','age'=>15]);

你可以在任何地方投递消息。

开启消费
\app\commands\Demo::consume();

你可以把消费者放到command命令行里面,使用命令行执行队列消费。举个例子: `php <?php

namespace app\commands;

use yii\console\Controller;

/**

  • @purpose 开启队列消费
  • @note 我只是一个例子 */ class QueueController extends Controller {

    /**

    • @api php yii queue/index
    • @return void
    • @throws \Exception
    • @comment 开启消费者 */ public function actionIndex() { Demo::consume(); } }
      开启消费者命令 consume
      ```bash
      php yii queue/index
      
      异常 Exception

队列使用过程中请使用 \RuntimeException和\Exception捕获异常

若需要使用延迟队列,那么rabbitmq服务需要安装延迟插件,否则会报错
联系作者:2723659854@qq.com
]]>
0
[extension] xiaosongshu/yii2-elasticsearch 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/xiaosongshu/yii2-elasticsearchhttps://yiiframework.cn/extension/xiaosongshu/yii2-elasticsearch 2723659854 2723659854

elasticsearch-YII客户端 elasticsearch client for YII

安装 install
composer require xiaosongshu/yii2-elasticsearch
配置 Configuration

`php

'components' => [

 'ESClient' => [
        'class' => \Xiaosongshu\Elasticsearch\ESClient::class,
        'node'=>['192.168.101.170:9200'],
        'username' => '',
        'password' => '',
    ],

] `

基本用法 example
$res = Yii::$app->ESClient->search('index','_doc','title','测试')['hits']['hits'];
客户端支持的所有方法
创建索引:createIndex
创建表结构:createMappings
删除索引:deleteIndex
获取索引详情:getIndex
新增一行数据:create
批量写入数据:insert
根据id批量删除数据:deleteMultipleByIds
根据Id 删除一条记录:deleteById
获取表结构:getMap
根据id查询数据:find
根据某一个关键字搜索:search
使用原生方式查询es的数据:nativeQuerySearch
多个字段并列查询,多个字段同时满足需要查询的值:andSearch
or查询  多字段或者查询:orSearch
根据条件删除数据:deleteByQuery
根据权重查询:searchByRank
获取所有数据:all
添加脚本:addScript
获取脚本:getScript
使用脚本查询:searchByScript
使用脚本更新文档:updateByScript
索引是否存在:IndexExists
根据id更新数据:updateById
如果单独使用本插件,则需要实例化的时候传入elasticsearch的连接配置
elasticsearch客户端使用实例
<?php
require_once 'vendor/autoload.php';

/** 实例化客户端 */
$client = new \Xiaosongshu\Elasticsearch\ESClient([
    /** 节点列表 */
    'nodes' => ['192.168.4.128:9200'],
    /** 用户名 */
    'username' => '',
    /** 密码 */
    'password' => '',
]);
/** 删除索引 */
$client->deleteIndex('index');
/** 如果不存在index索引,则创建index索引 */
if (!$client->IndexExists('index')) {
    /** 创建索引 */
    $client->createIndex('index', '_doc');
}

/** 创建表 */
$result = $client->createMappings('index', '_doc', [
    'id' => ['type' => 'long',],
    'title' => ['type' => 'text', "fielddata" => true,],
    'content' => ['type' => 'text', 'fielddata' => true],
    'create_time' => ['type' => 'text'],
    'test_a' => ["type" => "rank_feature"],
    'test_b' => ["type" => "rank_feature", "positive_score_impact" => false],
    'test_c' => ["type" => "rank_feature"],
]);
/** 获取数据库所有数据 */
$result = $client->all('index','_doc',0,15);

/** 写入单条数据 */
$result = $client->create('index', '_doc', [
    'id' => rand(1,99999),
    'title' => '我只是一个测试呢',
    'content' => '123456789',
    'create_time' => date('Y-m-d H:i:s'),
    'test_a' => 1,
    'test_b' => 2,
    'test_c' => 3,
]);
/** 批量写入数据 */
$result = $client->insert('index','_doc',[
    [
        'id' => rand(1,99999),
        'title' => '我只是一个测试呢',
        'content' => '你说什么',
        'create_time' => date('Y-m-d H:i:s'),
        'test_a' => rand(1,10),
        'test_b' => rand(1,10),
        'test_c' => rand(1,10),
    ],
    [
        'id' => rand(1,99999),
        'title' => '我只是一个测试呢',
        'content' => '你说什么',
        'create_time' => date('Y-m-d H:i:s'),
        'test_a' => rand(1,10),
        'test_b' => rand(1,10),
        'test_c' => rand(1,10),
    ],
    [
        'id' => rand(1,99999),
        'title' => '我只是一个测试呢',
        'content' => '你说什么',
        'create_time' => date('Y-m-d H:i:s'),
        'test_a' => rand(1,10),
        'test_b' => rand(1,10),
        'test_c' => rand(1,10),
    ],
]);
/** 使用关键字搜索 */
$result = $client->search('index','_doc','title','测试')['hits']['hits'];

/** 使用id更新数据 */
$result1 = $client->updateById('index','_doc',$result[0]['_id'],['content'=>'今天你测试了吗']);
/** 使用id 删除数据 */
$result = $client->deleteById('index','_doc',$result[0]['_id']);
/** 使用条件删除 */
$client->deleteByQuery('index','_doc','title','测试');
/** 使用关键字搜索 */
$result = $client->search('index','_doc','title','测试')['hits']['hits'];
/** 使用条件更新 */
$result = $client->updateByQuery('index','_doc','title','测试',['content'=>'哇了个哇,这么大的种子,这么大的花']);
/** 添加脚本 */
$result = $client->addScript('update_content',"doc['content'].value+'_'+'谁不说按家乡好'");
/** 添加脚本 */
$result = $client->addScript('update_content2',"(doc['content'].value)+'_'+'abcdefg'");
/** 获取脚本内容 */
$result = $client->getScript('update_content');
/** 使用脚本搜索 */
$result = $client->searchByScript('index', '_doc', 'update_content', 'title', '测试');
/** 删除脚本*/
$result = $client->deleteScript('update_content2');
/** 使用id查询 */
$result = $client->find('index','_doc','7fitkYkBktWURd5Uqckg');
/** 原生查询 */
$result = $client->nativeQuerySearch('index',[
    'query'=>[
        'bool'=>[
            'must'=>[
                [
                    'match_phrase'=>[
                        'title'=>'测试'
                    ],
                ],
                [
                    'script'=>[
                        'script'=>"doc['content'].value.length()>2"
                    ]
                ]
            ]
        ]
    ]

]);
/** and并且查询 */
$result = $client->andSearch('index','_doc',['title','content'],'测试');
/** or或者查询 */
$result = $client->orSearch('index','_doc',['title','content'],'今天');

测试

将本扩展包的phpunit.xml文件复制到项目的根目录下面然后执行下面的命令 `bash php ./vendor/bin/phpunit -c phpunit.xml `

联系作者

2723659854@qq.com

]]>
0
[extension] jatin 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/jatinhttps://yiiframework.cn/extension/jatin jatin_sharma jatin_sharma

$config['components']['mailer'] = [

'class' => 'jatin\resend\Mailer',
'useFileTransport' => false,
'viewPath' => '@app/mail',
'transport' => [
    'apiKey' => '<YOUR_API_KEY>'
],

];

]]>
0
[extension] asminog/yii2-proxy 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/asminog/yii2-proxyhttps://yiiframework.cn/extension/asminog/yii2-proxy asminog asminog

Yii 2 的 HTTP 代理扩展

  1. 安装
  2. 使用

这是一个针对 Yii2 框架的简单代理。此扩展为 Yii 框架 2.0 提供了 HTTP 代理操作。

有关许可证信息,请查看 LICENSE 文件。

Build Status Build Status

GitHub repo file count GitHub code size in bytes

安装

composer require asminog/yii2-proxy

使用

use asminog\proxy\ProxyAction;

class SiteController extends Controller
{
    public function actions()
    {
        return [
            'proxy' => [
                'class' => ProxyAction::class,
                // 'accessToken' => 'your-access-token', // - set access token for secure requests
                // 'throw404Exception' => true, // - show 404 error if access token is not valid or request url is not valid
                // 'proxyHeaders' => ['User-Agent', 'Content-Type'], // - set headers for proxy request
                // 'proxyCookies' => ['cookie1', 'cookie2'], // - set cookies for proxy request
            ],
        ];
    }
}
]]>
0
[extension] sandritsch91/yii2-widget-flatpickr 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/sandritsch91/yii2-widget-flatpickrhttps://yiiframework.cn/extension/sandritsch91/yii2-widget-flatpickr Sandritsch91 Sandritsch91

yii2-flatpickr

一个针对 Yii2flatpickr 小部件

]]>
0
[extension] sandritsch91/yii2-widget-form-wizard 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/sandritsch91/yii2-widget-form-wizardhttps://yiiframework.cn/extension/sandritsch91/yii2-widget-form-wizard Sandritsch91 Sandritsch91

yii2-form-wizard

  1. 特性
  2. 安装
  3. 使用
  4. 贡献

一个针对 bootstrap 5 的 Yii2 表单向导小部件

Alt preview

特性

  • Bootstrap 5
  • 客户端验证,可以选择单独验证每一步

安装

安装此扩展的首选方法是通过 composer

运行

php composer.phar require --prefer-dist sandritsch91/yii2-form-wizard

或添加

"sandritsch91/yii2-form-wizard": "*"

到您的 composer.json 文件的 require 部分。

使用

use sandritsch91\yii2-form-wizard\FormWizard;

echo FormWizard::widget([
    // required
    'model' => $model,                                                          // The model to be used in the form
    'tabOptions' => [                                                           // These are the options for the Bootstrap Tab widget                                        
        'items' => [
            [
                'label' => 'Step 1',                                            // The label of the tab, if omitted, a default-label will be used (Step 1, Step 2, ...)
                'content' => $this->render('_step1', ['model' => $model]),      // Either the content of the tab
            ],
            [
                'label' => 'Step 2',
                'view' => '/test/_step2',                                       // or a view to be rendered. $model and $form are passed to the view
                'params' => ['a' => 1, 'b' => 2]                                // Pass additional parameters to the view
            ]
        ],
        'navType' => 'nav-pills'
    ],
    // optional
    'validateSteps' => [                                                        // Optional, pass the fields to be validated for each step.                 
        ['name', 'surname'],
        [],                                                                     // Leave array empty if no validation is needed  
        ['email', 'password']
    ],
    'options' => [],                                                            // Wizard-container html options
    'formOptions' => [],                                                        // Form html options
    'buttonOptions' => [                                                        // Button html options
        'previous' => [
            'class' => ['btn', 'btn-secondary'],
            'data' => [
                'formwizard' => 'previous'                                      // If you change this, make sure the clientOptions match
            ]
        ],
        'next' => [...],
        'finish' => [...]
    ],
    'clientOptions' => [                                                        // Client options for the form wizard, if you need to change them
        // 'finishSelector' => '...',
        // 'nextSelector' => '...',
        // 'previousSelector' => '...',
        // 'keepPosition' => true                                               // Keep scroll position on step change.
                                                                                // Set to false to disable, or pass a selector if you have a custom scroll container.
                                                                                // Defaults to true.
    ],
    'clientEvents' => [                                                         // Client events for the form wizard
        // 'onNext' => 'function () {...}',
        // 'onPrevious' => 'function () {...}',
        // 'onFinish' => 'function (){...}'
    ]
]);

贡献

欢迎贡献。

如果您有任何问题、想法、建议或错误,请打开一个问题。

测试

此包使用 codeception 进行测试。要运行测试,请运行以下命令


#### Unit tests

run ```php.exe .\vendor\bin\codecept run Unit``` in the root directory of this repository.

#### Functional tests

run ```php.exe .\vendor\bin\codecept run Functional``` in the root directory of this repository.

#### Accpetance tests

To be able to run acceptance tests, a few requirements are needed:

For Windows:\

- install java runtime environment
- install nodejs
- install selenium-standalone: `npm install -g selenium-standalone`
- start selenium-standalone: `selenium-standalone install && selenium-standalone start`
- host a yii2 application on a server or locally via ```./yii serve```
    - add this plugin as a dependency to your ```composer.json``` and update dependencies
    - site must be reachable over http://formwizard.com/
    - add an action ```actionTest``` to the ```SiteController```, as described below
    - this action must return a view file, as described below
    - run ```php.exe .\vendor\bin\codecept run Acceptance```

For Linux:  
Never did that before, but I think it is similar to the Windows setup.

The action in the SiteController:

```php
public function actionTest(): string
{
    include __DIR__ . '/../vendor/sandritsch91/yii2-widget-form-wizard/tests/Support/Data/models/User.php';

    $model = new User();

    if (Yii::$app->request->post() && $model->load(Yii::$app->request->post()) && $model->validate()) {
        return 'success';
    }

    return $this->render('test', [
        'model' => new User()
    ]);
}
```

The view returned by the action:

```php
/** @var User $model */

use sandritsch91\yii2\formwizard\FormWizard;
use sandritsch91\yii2\formwizard\tests\Support\Data\models\User;

$wizard = FormWizard::widget([
    'model' => $model,
    'tabOptions' => [
        'options' => [
            'class' => 'mb-3'
        ],
        'items' => [
            [
                'label' => 'Step 1',
                'view' => '@app/vendor/sandritsch91/yii2-widget-form-wizard/tests/Support/Data/views/site/step1',
                'linkOptions' => [
                    'id' => 'step1-link',,
                    'params' => [
                        'test' => 'some test variable'
                    ]
                ]
            ],
            [
                'label' => 'Step 2',
                'view' => '@app/vendor/sandritsch91/yii2-widget-form-wizard/tests/Support/Data/views/site/step2',
                'linkOptions' => [
                    'id' => 'step2-link',
                ]
            ],
            [
                'label' => 'Step 3',
                'view' => '@app/vendor/sandritsch91/yii2-widget-form-wizard/tests/Support/Data/views/site/step3',
                'linkOptions' => [
                    'id' => 'step3-link',
                ]
            ]
        ],
        'navType' => 'nav-pills'
    ],
    'validateSteps' => [
        ['firstname', 'lastname'],
        ['username', 'password', 'password_validate'],
        ['email']
    ],
    'clientOptions' => [
        'keepPosition' => true
    ]
]);

echo \yii\helpers\Html::tag('div', $wizard, [
    'class' => 'col-4'
]);
```

After the initial installation, you only have to start the selenium-standalone server ```selenium-standalone start```
and run the tests ```php.exe .\vendor\bin\codecept run Acceptance``` in the root directory of this repository.

If you do not want to setup an application, just run the unit and functional tests by
running ```php.exe .\vendor\bin\codecept run Unit,Functional```, I can modify and run the acceptance tests for you,
after you opened a pull request.

]]>
0
[wiki] 将 Yii3 包集成到 WordPress 中 Mon, 04 Mar 2024 16:34:16 +0000 https://yiiframework.cn/wiki/2579/integrating-yii3-packages-into-wordpresshttps://yiiframework.cn/wiki/2579/integrating-yii3-packages-into-wordpress glpzzz glpzzz
  1. 源代码可用
  2. 目标
  3. 方法
  4. 结论

我最近被分配的任务是将几个扩展表单集成到 WordPress 网站中。这些表单包含许多字段、复杂的验证规则、动态字段(一对多关系),甚至相互依赖关系,其中使用 PHP 继承可以减少代码重复。

在最初的探索中,很明显,在 WordPress 中处理表单的传统方法通常涉及安装插件或使用编辑器或自定义页面模板手动嵌入标记。随后,很大程度上依赖于插件的功能来管理表单提交,或者诉诸自定义编码。

鉴于我的任务的一部分包括记录数据、与 API 端点交互、发送电子邮件等等,我选择自己开发功能,而不是验证现有插件是否支持这些要求。

此外,考虑到当前情况(截至 2024 年 3 月),大多数 Yii 3 包被认为是生产就绪的,根据官方资料,并且作为 Yii 框架的长期用户,我认为这是一个探索和熟悉这些更新的绝佳时机。

源代码可用

您可以通过访问 Github 上的整个项目来探索并查看代码。

此外,您可以轻松地使用 Docker 部署它,只需从项目的根目录执行 docker-compose up 即可。检查 Dockerfile 以获取 WordPress 设置和内容生成,这些操作会自动完成。

目标

我的目标是在利用 Yii3 包的 WordPress 框架中呈现和管理表单。为了演示目的,我选择实现一个基本的评分表单,其中重点仅在于验证数据,而不执行进一步的操作。

方法

要继续,让我们从一个极简的经典主题作为示例开始。我在仪表盘中创建了一个名为“评分表单”的 WordPress 页面。然后,需要在主题的根文件夹中创建一个名为 page-the-rating-form.php 的文件来显示此特定页面。

此指定文件用作定义表单标记的蓝图。

将 Yii3 包添加到项目中:

为了利用 Yii3 的功能,我们将合并以下包

首先,让我们通过执行 composer init 在主题的根目录中初始化一个 Composer 项目。此过程将生成一个 composer.json 文件。随后,我们将继续将 Yii3 包包含到我们的项目中。

composer require yiisoft/form-model:dev-master yiisoft/validator yiisoft/form:dev-master

并指示主题加载 composer 自动加载,方法是将以下行添加到 functions.php 文件中

require __DIR__ . '/vendor/autoload.php';
创建表单模型

执行 composer init 命令后,将在主题的根目录中创建一个 src 目录。现在我们将继续在此目录中添加我们的表单模型类。

预见项目的扩展,必须保持组织。因此,我们将创建目录 src/Forms 并将 RatingForm 类放置在其中。

<?php

namespace Glpzzz\Yii3press\Forms;

use Yiisoft\FormModel\FormModel;

class RatingForm extends FormModel
{

	private ?string $name = null;
	private ?string $email = null;
	private ?int $rating = null;
	private ?string $comment = null;
	private string $action = 'the_rating_form';

	public function getPropertyLabels(): array
	{
		return [
			'name' => 'Name',
			'email' => 'Email',
			'rating' => 'Rating',
			'comment' => 'Comment',
		];
	}

}

除了我们评分用例所需的必填字段之外,观察 action 类属性至关重要。此属性非常重要,因为它指示 WordPress 哪个主题钩子应该管理表单提交。对此将作进一步说明。

向模型添加验证规则:

现在,让我们将一些验证规则合并到模型中,以确保输入完整性。最初,我们将配置类以实现 RulesProviderInterface。这使表单包能够访问这些规则并使用本机验证属性增强 HTML 标记。

class RatingForm extends FormModel implements RulesProviderInterface

现在我们需要在类上实现 getRules() 方法。

public function getRules(): iterable
{
	return [
		'name' => [
			new Required(),
		],
		'email' => [
			new Required(),
			new Email(),
		],
		'rating' => [
			new Required(),
			new Integer(min: 0, max: 5),
		],
		'comment' => [
			new Length(min: 100),
		],
	];
}
创建表单标记

要生成表单标记,我们需要将 RatingForm 的实例传递给模板。在 WordPress 中,我采用的方法是在呈现页面之前创建一个全局变量(不可否认,这不是最优雅的解决方案)。


$hydrator = new Hydrator(
	new CompositeTypeCaster(
		new NullTypeCaster(emptyString: true),
		new PhpNativeTypeCaster(),
		new HydratorTypeCaster(),
	)
);

add_filter('template_redirect', function () use ($hydrator) {
	// Get the queried object
	$queried_object = get_queried_object();

	// Check if it's a page
	if ($queried_object instanceof WP_Post && is_page()) {
		if ($queried_object->post_name === 'the-rating-form') {
			global $form;
			if ($form === null) {
				$form = $hydrator->create(RatingForm::class, []);
			}
		}
	}
});

值得注意的是,我们已在任何特定函数之外实例化了 Hydrator 类,使我们能够将其重新用于所有必要的回调。现在 RatingForm 实例可用后,我们将继续在 page-the-rating-form.php 文件中为表单制作标记。


<?php

use Glpzzz\Yii3press\Forms\RatingForm;
use Yiisoft\FormModel\Field;
use Yiisoft\Html\Html;

/** @var RatingForm $form */
global $form;

?>


<?php get_header(); ?>

<h1><?php the_title(); ?></h1>

<?php the_content(); ?>

<?= Html::form()
  ->post(esc_url(admin_url('admin-post.php')))
  ->open()
?>

<?= Field::hidden($form, 'action')->name('action') ?>
<?= Field::text($form, 'name') ?>
<?= Field::email($form, 'email') ?>
<?= Field::range($form, 'rating') ?>
<?= Field::textarea($form, 'comment') ?>

<?= Html::submitButton('Send') ?>

<?= "</form>" ?>

<?php get_footer(); ?>

在表单的标记生成中,我们利用了 Yii3 的 Html 助手和 Field 类的组合。值得注意的要点包括

  • 表单使用 POST 方法,其操作指定为 admin-post.php WordPress 端点。
  • 为了在表单提交中包含 action 值,我们使用了一个名为 'action' 的隐藏字段。我们选择将输入重命名为 'action',因为 Field::hidden 方法以 TheFormClassName[the_field_name] 格式生成字段名,而我们需要将其简单地命名为 'action'

此调整便于挂钩到主题函数以处理表单请求,如下一节所述。

在深入研究之前,让我们利用 Yii 的功能来增强表单。虽然我们已经在模型中定义了用于验证提交后输入的验证规则,但在浏览器中验证输入也是有利的。虽然我们可以重复定义这些验证规则直接在输入元素上,但 Yii 提供了一种简化的方式。通过将以下代码片段合并到 functions.php 文件中

add_action('init', function () {
	ThemeContainer::initialize([
			'default' => [
				'enrichFromValidationRules' => true,
			]
		], 'default', new ValidationRulesEnricher()
	);
});

通过实现此代码片段,我们为默认的表单主题激活了 ValidationRulesEnricher。激活后,我们会注意到表单字段现在被丰富了验证规则,例如“required”、“min”和“max”,与之前在模型类中定义的验证规则一致。此功能简化了流程,节省了宝贵的时间,并最大程度地减少了手动编写代码的需求。事实上,这展示了 Yii3 提供的一些出色功能。

处理 POST 请求

当表单提交时,它被定向到 admin-post.php,这是一个由 WordPress 提供的端点。但是,当处理多个表单时,区分每个表单的处理变得至关重要。这就是在 POST 请求中包含 action 值非常宝贵的地方。

请注意以下代码片段中的前两行:钩子的命名约定是 admin_post_<action_name>。因此,如果一个表单有 action = 'the-rating-form',相应的钩子名称将是 admin_post_the_rating_form

至于同时包含 admin_post_<action_name>admin_post_nopriv_<action_name>,这是因为 WordPress 允许使用不同的处理程序,具体取决于用户是否登录。在我们的场景中,我们要求相同的处理程序,无论用户的身份验证状态如何。

add_action('admin_post_the_rating_form', fn() => handleForms($hydrator));
add_action('admin_post_nopriv_the_rating_form', fn() => handleForms($hydrator));

function handleForms(Hydrator $hydrator): void
{
  global $form;
  $form = $hydrator->create(RatingForm::class, $_POST['RatingForm']);
  $result = (new Yiisoft\Validator\Validator())->validate($form);

  if ($form->isValid()) {
    // handle the form
  }

  get_template_part('page-the-rating-form');
}

回到 Yii 方面:我们使用 hydrator 实例化并加载发布的数据到表单中。然后,我们继续验证数据。如果验证成功通过,我们可以使用经过验证的数据继续执行预期操作。但是,如果验证失败,我们将重新渲染表单,并使用提交的数据以及验证期间生成的任何错误消息填充它。

结论

  • 这是我第一次尝试将 Yii3 包与 WordPress 网站混合使用。虽然我对结果感到满意,但我认为可以改进它,尤其是在使用全局变量方面。由于我对 WordPress 不太熟悉,我感谢任何改进建议
  • 我使用的 Yii3 包已准备好用于实际应用,并提供与其旧版本相同的功能和质量。
  • 现在,您可以独立使用这些 Yii 包。这意味着您可以将您的 Yii 技能应用于任何 PHP 项目。
  • 这个项目展示了我们如何通过利用 Yii 的强大功能来增强 WordPress 网站,同时仍然保持 CMS 的简洁性。

最初发布于 https://glpzzz.dev/2024/03/03/integrating-yii3-packages-into-wordpress.html

]]>
0
[扩展] neoacevedo/yii2-auditing Wed, 28 Feb 2024 00:31:28 +0000 https://yiiframework.cn/extension/neoacevedo/yii2-auditinghttps://yiiframework.cn/extension/neoacevedo/yii2-auditing NestorAcevedo NestorAcevedo

Yii2 Auditing

  1. 安装
  2. 使用
  3. 部署信息

记录 Yii2 的 ActiveRecord 模型的更改。

此包允许维护模型更改的历史记录,提供有关可能差异或异常的信息,这些信息可能表明可疑活动。接收和存储的信息可以随后以多种方式部署。

安装

安装此扩展的最佳方式是通过 composer

然后执行

php composer.phar require --prefer-dist neoacevedo/yii2-auditing "*"

或添加

"neoacevedo/yii2-auditing": "*"

到您的 composer.json 文件的 require 部分。

使用

安装扩展后,在应用程序的控制台配置文件中,在 migrationPath 区域添加

...
'@vendor/neoacevedo/yii2-auditing/neoacevedo/auditing/migrations',
...

然后,在模型代码中,在 behaviors 方法内添加

public function behaviors()
{
    return [
        [
                'class' => \neoacevedo\auditing\behaviors\AuditBehavior::class,
                'deleteOldData' => true, // Para borrar datos antiguos del registro de eventos
                'deleteNumRows' => 20, // Borra esta cantidad de registros
        ],
        ...
    ];
}

部署信息

您可以像在 Web 应用程序中实现的任何模型一样部署信息。

您可以使用一个控制器和一个使用 GridView 的视图来列出历史记录。例如,您可以创建一个名为 AuditingController 的控制器,并创建如下的 actionIndex 方法

    /**
     * Lists all Auditing models.
     *
     * @return string
     */
    public function actionIndex()
    {
        $searchModel = new AuditingSearch();
        $dataProvider = $searchModel->search($this->request->queryParams);

        return $this->render('index', [
            'searchModel' => $searchModel,
            'dataProvider' => $dataProvider,
        ]);
    }

要查看数据,请创建 actionView 方法

    /**
     * Displays a single Auditing model.
     * @param int $id ID
     * @return string
     * @throws NotFoundHttpException if the model cannot be found
     */
    public function actionView($id)
    {
        return $this->render('view', [
            'model' => $this->findModel($id),
        ]);
    }

view 视图中,您可以添加 GridView 来列出历史记录

...
    <?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            ['class' => 'yii\grid\SerialColumn'],
            'id',
            'user_id',
            'description',
            'event',
            'model',
            'attribute',
            'old_value',
            'new_value',
            'action',
            'ip',
            'created_at',
        ],
    ]); ?>
...
]]>
0
[扩展] luguohuakai/yii2-dm 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/luguohuakai/yii2-dmhttps://yiiframework.cn/extension/luguohuakai/yii2-dm luguohuakai luguohuakai

DM 数据库扩展

  1. 安装
  2. 使用

用于 DM 数据库的数据库扩展

安装

安装此扩展的首选方法是通过 composer

运行

php composer.phar require --prefer-dist luguohuakai/yii2-dm "*"

或添加

"luguohuakai/yii2-dm": "*"

到您的 composer.json 文件的 require 部分。

使用

安装扩展后,只需在代码中使用它

'components' => [
    'db' => [
        'class' => 'luguohuakai\db\dm\Connection',
        'dsn' => 'dm:host=localhost:xxx;schema=xxx',
        'username' => 'SYSDBA',
        'password' => 'SYSDBA',
    ]
]
]]>
0
[扩展] rashedalkhatib/yii2-datatables 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/rashedalkhatib/yii2-datatableshttps://yiiframework.cn/extension/rashedalkhatib/yii2-datatables RashedAlkhatib RashedAlkhatib

DataTable 小部件

  1. 概述
  2. 安装
  3. 使用示例 (PHP 小部件)
  4. 使用示例 (JavaScript)
  5. 使用 API 侧
  6. 随时联系我:alkhatib.rashed@gmail.com

概述

DataTable 小部件用于创建交互式和动态数据表。提供的 JavaScript 代码演示了如何使用服务器端处理、自定义数据处理以及列渲染和完整服务器端导出来初始化 DataTable。

安装

在您的 Yii2 应用程序中:
  • 运行:`composer require rashedalkhatib/yii2-datatables:1.0.0`
  • 转到您的 `../frontend/assets/AppAsset.php`
    • rashedalkhatib\datatables\DataTableAsset 添加到您的 $depends 数组中
    •         public $depends = [
                'yii\web\YiiAsset',
                'yii\bootstrap\BootstrapAsset',
                'yii\bootstrap\BootstrapPluginAsset',
                'rashedalkhatib\datatables\DataTableAsset'
        ];
      

使用示例 (PHP 小部件)

- 应用程序端
$searchFormSelector = '#searchForm';
$ajaxUrl = Url::to(['api/yourEndPoint']); // Adjust the URL based on your routes

// Define your DataTable columns
$columns = [
    [
        'title' => 'ID',
        'data' => 'id',
        'visible' => true,
        'render' => new JsExpression('function(data, type, row) {
            return "demo";
        }'),
    ],
];

// Configure other DataTable parameters
$processing = true;
$serverSide = true;
$pageLength = 10;
$dom = 'Btip';
$buttons = [
    [
        'extend' => 'excel',
        'text' => 'Excel',
        'titleAttr' => 'Excel',
        'action' => new JsExpression('exportAll') // this is required 
    ],
];

// Configure Ajax settings
$ajaxConfig = [
    'url' => $ajaxUrl,
    'bdestroy' => true,
    'type' => 'POST',
    'data' => new JsExpression('function(d) {
            var searchForm = $('body').find('#searchForm').serializeArray();
            
            searchForm[searchForm.length] = { name: 'YourModel[page]', value: d.start }; // required
            searchForm[searchForm.length] = { name: 'YourModel[length]', value: d.length }; // required
            searchForm[searchForm.length] = { name: 'YourModel[draw]', value: d.draw }; // required
            
            var order = {
                'attribute': d.columns[d.order[0]['column']]['data'],
                'dir': d.order[0]['dir']
            }; // required
            
            searchForm[searchForm.length] = { name: 'YourModel[order]', value: JSON.stringify(order) };
            return searchForm;
    }'),
    'dataSrc' => new JsExpression('function(d) {
        var searchForm = $("' . $searchFormSelector . '").serializeArray();
        if (d.validation) {
            searchForm.yiiActiveForm("updateMessages", d.validation, true);
            return [];
        }
        return d.data;
    }'),
];

// Use the DataTableWidget with configured parameters
DataTable::widget([
    'id' => 'yourDataTable',
    'ajaxConfig' => $ajaxConfig,
    'columns' => $columns,
    'processing' => $processing,
    'serverSide' => $serverSide,
    'pageLength' => $pageLength,
    'dom' => $dom,
    'buttons' => $buttons,
]);

// The HTML container for your DataTable
echo '<form id="searchForm">// your inputs </form>';
echo '<table id="yourDataTable" class="display"></table>';

使用示例 (JavaScript)

- 应用程序端
前端
<form id="searchForm">
// your inputs 
</form>

<table id="yourDataTable" class="display" style="width:100%">


</table>
var arrayToExport = [0,1];
$('#yourDataTable').DataTable({
    "ajax": {
        // Server-side processing configuration
        "url": "../api/yourEndPoint",
        "bdestroy": true, // this allows you to re init the dataTabel and destory it 
        "type": "POST", // request method
        "data": function (d) { // this represent the data you are sending with your ajax request
            // Custom function for sending additional parameters to the server
            var searchForm = $('body').find('#searchForm').serializeArray();
            
            searchForm[searchForm.length] = { name: "YourModel[page]", value: d.start }; // required
            searchForm[searchForm.length] = { name: "YourModel[length]", value: d.length }; // required
            searchForm[searchForm.length] = { name: "YourModel[draw]", value: d.draw }; // required
            
            var order = {
                'attribute': d.columns[d.order[0]['column']]['data'],
                'dir': d.order[0]['dir']
            }; // required
            
            searchForm[searchForm.length] = { name: "YourModel[order]", value: JSON.stringify(order) };
            return searchForm;
        },
        dataSrc: function (d) {
            // Custom function to handle the response data
            // EX:
            var searchForm = $('body').find('#searchForm').serializeArray();
            if (d.validation) {
                searchForm.yiiActiveForm('updateMessages', d.validation, true);
                return [];
            }
            return d.data;
        }
    },
    "columns": [{
        // Column configurations
        "title": "ID",
        "data": "id",
        "visible": true // visablity of column 
    },
    // ... (other columns)
    {
        "title": "Actions",
        "data": "id",
        "visible": actionCol,
        "render": function (data, type, row) {
            // Custom rendering function for the "Actions" column
            return '<a class="showSomething" data-id="' + row.id + '">View</a>';
        }
    }],
    processing: true,
    serverSide: true,
    "pageLength": 10,
    dom: "Btip",
    "buttons": [{
        // "Excel" button configuration
        "extend": 'excel',
        exportOptions: {
            columns: arrayToExport
        },
        "text": '  Excel',
        "titleAttr": 'Excel',
        "action": exportAll // newexportaction this action is to allow you exporting with server side without rendaring data 
    }],
});
应用程序后端
这些参数应发送到 API
// in your HTTP request you want to include these params 
   $_postData = [
   'page' => $this->page == 0 ? 0 : $this->page / $this->length, // this equation is required to handle Yii2 Data provider Logic
   'limit' => $this->length,
   'export' => $this->export,
   'order' => $this->order,
   // add your custom params .....
   ];
这些参数应返回到 Datatable 端点
return $this->asJson(
                    [
                        'data' => $_scoreData->data,
                        'draw' => $_scoreSearchForm->draw,
                        'recordsTotal' => $_scoreData->count, 
                        'recordsFiltered' => $_scoreData->count
                ]);

使用 API 侧

您的端点操作
    public function actionYourEndPoint()
    {

        $searchModel = new SearchModel();

        $dataProvider = $searchModel->search(Yii::$app->request->get());
        return $this->asJson(
            array(
                'data' => $dataProvider['data'],
                'count' => $dataProvider['count']
            )
        );

    }
搜索函数
    public function search($params)
    {
        $this->load($params, ''); // load your values into the model
        $query = Data::find(); // Data model is your link to the database

        $_order = json_decode($this->order);
        if ($this->export == 'true') {
            $dataProvider = new ActiveDataProvider([
                'query' => $query
                // we removed the page and pageSize keys to allow all data to be exported
            ]);
        } else {
            $_orderType = SORT_ASC;
            if ($_order->dir == 'desc')
                $_orderType = SORT_DESC;
            $query->orderBy([$_order->attribute => $_orderType]);
            $dataProvider = new ActiveDataProvider([
                'query' => $query,
                'pagination' => [
                    'pageSize' => $this->limit,
                    'page' => $this->page,
                ],
            ]);
        }


        return array(
            'data' => $dataProvider->getModels(),
            'count' => $dataProvider->getTotalCount()
        );
    }

随时联系我:alkhatib.rashed@gmail.com

]]>
0
[扩展] eluhr/yii2-json-attribute-behavior 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/eluhr/yii2-json-attribute-behaviorhttps://yiiframework.cn/extension/eluhr/yii2-json-attribute-behavior eluhr eluhr

Yii2 JSON 属性行为

  1. 安装
  2. 使用
  3. 测试

此行为会在验证之前自动将属性从 JSON 解码为数组,处理错误并在验证失败时重新编码。这样,就可以进一步处理“真实”json 字符串。

CI Workflow

安装

安装此扩展的最佳方式是通过 composer

运行

composer require --prefer-dist eluhr/yii2-json-attribute-behavior "*"

或添加

"eluhr/yii2-json-attribute-behavior": "*"

到您的 composer.json 文件的 require 部分。

使用

yii\base\Model 或其派生类中,行为的使用方式如下

public function behaviors(): array
{
    $behaviors = parent::behaviors();
    $behaviors['json-attribute'] = [
        'class' => eluhr\jsonAttributeBehavior\JsonAttributeBehavior::class,
        'attributes' => [
            'data_json'
        ]
    ];
    return $behaviors;
}

通过使用此行为,属性是字符串还是数组并不重要。此行为始终确保在将数据保存到数据库之前,属性是一个数组,yii 将处理其余工作。

此行为支持 i18n。通过在您的配置中添加 json-attribute-behavior 类别,您可以覆盖默认的错误消息。

测试

通过 composer 安装依赖项后,您可以使用以下命令运行测试

make test
]]>
0
[扩展] ip2location/ip2proxy-yii 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/ip2location/ip2proxy-yiihttps://yiiframework.cn/extension/ip2location/ip2proxy-yii hexasoft hexasoft

IP2Proxy Yii 扩展

  1. 安装
  2. 使用
  3. 依赖项
  4. 支持

IP2Proxy Yii 扩展允许用户查询一个 IP 地址,以确定它是否被用作开放代理、Web 代理、VPN 匿名器和 TOR 出口节点、搜索引擎机器人、数据中心范围、住宅代理、消费者隐私网络和企业私有网络。它从 IP2Proxy BIN 数据文件或 Web 服务中查找代理 IP 地址。开发人员可以使用 API 查询所有 IP2Proxy BIN 数据库或 Web 服务,以用于使用 Yii 编写的应用程序。

安装

对于 Yii2

  1. 运行命令:php composer.phar require ip2location/ip2proxy-yii 将插件下载到 Yii2 框架中。
  2. 下载最新的 IP2Proxy BIN 数据库
  3. 解压缩并将 BIN 文件复制到 Yii2 框架中。

注意:BIN 数据库指的是以 .BIN 扩展名结尾的二进制文件,而不是 CSV 格式。请选择正确的包进行下载。

使用

use IP2ProxyYii\IP2Proxy_Yii;

// (required) Define IP2Proxy database path.
define('IP2PROXY_DATABASE', '/path/to/ip2proxy/database');

// (required) Define IP2Location.io API key.
define('IP2LOCATION_IO_API_KEY', 'your_api_key');

// (optional) Define Translation information. Refer to https://www.ip2location.io/ip2location-documentation for available languages.
define('IP2LOCATION_IO_LANGUAGE', 'en');

$IP2Proxy = new IP2Proxy_Yii();

$record = $IP2Proxy->get('1.0.241.135');
echo 'Result from BIN Database:<br>';
echo '<p><strong>IP Address: </strong>' . $record['ipAddress'] . '</p>';
echo '<p><strong>IP Number: </strong>' . $record['ipNumber'] . '</p>';
echo '<p><strong>IP Version: </strong>' . $record['ipVersion'] . '</p>';
echo '<p><strong>Country Code: </strong>' . $record['countryCode'] . '</p>';
echo '<p><strong>Country: </strong>' . $record['countryName'] . '</p>';
echo '<p><strong>State: </strong>' . $record['regionName'] . '</p>';
echo '<p><strong>City: </strong>' . $record['cityName'] . '</p>';
echo '<p><strong>Proxy Type: </strong>' . $record['proxyType'] . '</p>';
echo '<p><strong>Is Proxy: </strong>' . $record['isProxy'] . '</p>';
echo '<p><strong>ISP: </strong>' . $record['isp'] . '</p>';
echo '<p><strong>Domain: </strong>' . $record['domain'] . '</p>';
echo '<p><strong>Usage Type: </strong>' . $record['usageType'] . '</p>';
echo '<p><strong>ASN: </strong>' . $record['asn'] . '</p>';
echo '<p><strong>AS: </strong>' . $record['as'] . '</p>';
echo '<p><strong>Last Seen: </strong>' . $record['lastSeen'] . '</p>';
echo '<p><strong>Threat: </strong>' . $record['threat'] . '</p>';
echo '<p><strong>Provider: </strong>' . $record['provider'] . '</p>';

$record = $IP2Proxy->getWebService('1.0.241.135');
echo 'Result from Web service:<br>';
echo '<pre>';
print_r ($record);
echo '</pre>';

依赖项

此库需要 IP2Proxy BIN 或 IP2Proxy API 密钥数据文件才能正常运行。您可以在以下位置下载 BIN 数据文件:

您还可以注册 IP2Location.io IP 地理位置 API 以获取一个免费的 API 密钥。

支持

电子邮件:support@ip2location.com

网站:https://www.ip2location.com

]]>
0
[扩展] ip2location/ip2location-yii 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/ip2location/ip2location-yiihttps://yiiframework.cn/extension/ip2location/ip2location-yii hexasoft hexasoft

IP2Location Yii 扩展

  1. 安装
  2. 使用
  3. 依赖项
  4. 支持

IP2Location Yii 扩展允许用户使用 IP2Location 数据库从 IP 地址中查找国家、地区、城市、坐标、邮政编码、时区、ISP、域名、连接类型、区号、天气、MCC、MNC、移动品牌名称、海拔、使用类型、IP 地址类型和 IAB 广告类别。它针对速度和内存使用率进行了优化。开发人员可以使用 API 查询所有 IP2Location BIN 数据库或 Web 服务,以用于使用 Yii 编写的应用程序

安装

对于 Yii2

  1. 运行命令:composer require ip2location/ip2location-yii 将扩展下载到 Yii2 框架中。
  2. 下载最新的 IP2Location BIN 数据库
  3. 解压缩并将 BIN 文件复制到 Yii2 框架中。

注意:BIN 数据库指的是以 .BIN 扩展名结尾的二进制文件,而不是 CSV 格式。请选择正确的包进行下载。

使用

use IP2LocationYii\IP2Location_Yii;

// (required) Define IP2Location database path.
define('IP2LOCATION_DATABASE', '/path/to/ip2location/database');

// (required) Define IP2Location.io API key.
define('IP2LOCATION_IO_API_KEY', 'your_api_key');

// (optional) Define Translation information. Refer to https://www.ip2location.io/ip2location-documentation for available languages.
define('IP2LOCATION_IO_LANGUAGE', 'en');

// (optional) Define Translation information. Refer to https://www.ip2location.com/web-service/ip2location for available languages.
define('IP2LOCATION_LANGUAGE', 'en');

$IP2Location = new IP2Location_Yii();

$record = $IP2Location->get('8.8.8.8');
echo 'Result from BIN Database:<br>';
echo 'IP Address: ' . $record['ipAddress'] . '<br>';
echo 'IP Number: ' . $record['ipNumber'] . '<br>';
echo 'ISO Country Code: ' . $record['countryCode'] . '<br>';
echo 'Country Name: ' . $record['countryName'] . '<br>';
echo 'Region Name: ' . $record['regionName'] . '<br>';
echo 'City Name: ' . $record['cityName'] . '<br>';
echo 'Latitude: ' . $record['latitude'] . '<br>';
echo 'Longitude: ' . $record['longitude'] . '<br>';
echo 'ZIP Code: ' . $record['zipCode'] . '<br>';
echo 'Time Zone: ' . $record['timeZone'] . '<br>';
echo 'ISP Name: ' . $record['isp'] . '<br>';
echo 'Domain Name: ' . $record['domainName'] . '<br>';
echo 'Net Speed: ' . $record['netSpeed'] . '<br>';
echo 'IDD Code: ' . $record['iddCode'] . '<br>';
echo 'Area Code: ' . $record['areaCode'] . '<br>';
echo 'Weather Station Code: ' . $record['weatherStationCode'] . '<br>';
echo 'Weather Station Name: ' . $record['weatherStationName'] . '<br>';
echo 'MCC: ' . $record['mcc'] . '<br>';
echo 'MNC: ' . $record['mnc'] . '<br>';
echo 'Mobile Carrier Name: ' . $record['mobileCarrierName'] . '<br>';
echo 'Elevation: ' . $record['elevation'] . '<br>';
echo 'Usage Type: ' . $record['usageType'] . '<br>';
echo 'Address Type: ' . $record['addressType'] . '<br>';
echo 'Category: ' . $record['category'] . '<br>';

$record = $IP2Location->getWebService('8.8.8.8');
echo 'Result from Web service:<br>';
echo '<pre>';
print_r ($record);
echo '</pre>';

依赖项

此库需要 IP2Location BIN 数据文件或 IP2Location API 密钥才能运行。您可以从以下位置下载 BIN 数据文件:

您还可以注册 IP2Location.io IP 地理位置 API 以获取一个免费的 API 密钥。

支持

电子邮件:support@ip2location.com

网站:https://www.ip2location.com

]]>
0
[wiki] 使用 Bootstrap5 创建带有缩略图的图片轮播 周一,2023 年 12 月 4 日 13:03:38 +0000 https://yiiframework.cn/wiki/2578/create-bootstrap5-based-image-carousel-with-thumbnailshttps://yiiframework.cn/wiki/2578/create-bootstrap5-based-image-carousel-with-thumbnails pravi pravi

使用以下 CSS 样式让轮播按预期工作。


  .product_img_slide {
    padding: 100px 0 0 0;
  }

  .product_img_slide > .carousel-inner > .carousel-item {
    overflow: hidden;
    max-height: 650px;
  }

  .carousel-inner {
    position: relative;
    width: 100%;
  }

  .product_img_slide > .carousel-indicators {
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    bottom: auto;
    margin: auto;
    font-size: 0;
    cursor: e-resize;
    /* overflow-x: auto; */
    text-align: left;
    padding: 10px 5px;
    /*  overflow-y: hidden;*/
    white-space: nowrap;
    position: absolute;
  }

  .product_img_slide > .carousel-indicators li {
    padding: 0;
    width: 76px;
    height: 76px;
    margin: 0 5px;
    text-indent: 0;
    cursor: pointer;
    background: transparent;
    border: 3px solid #333331;
    -webkit-border-radius: 0;
    border-radius: 0;
    -webkit-transition: all 0.7s cubic-bezier(0.22, 0.81, 0.01, 0.99);
    transition: all 1s cubic-bezier(0.22, 0.81, 0.01, 0.99);
  }

  .product_img_slide > .carousel-indicators .active {
    width: 76px;
    border: 0;
    height: 76px;
    margin: 0 5px;
    background: transparent;
    border: 3px solid #c13c3d;
  }

  .product_img_slide > .carousel-indicators > li > img {
    display: block;
    /*width:114px;*/
    height: 76px;
  }

  .product_img_slide .carousel-inner > .carousel-item > a > img, .carousel-inner > .carousel-item > img, .img-responsive, .thumbnail a > img, .thumbnail > img {
    display: block;
    max-width: 100%;
    line-height: 1;
    margin: auto;
  }

  .product_img_slide .carousel-control-prev {
    top: 58%;
    /*left: auto;*/
    right: 76px;
    opacity: 1;
    width: 50px;
    bottom: auto;
    height: 50px;
    font-size: 50px;
    cursor: pointer;
    font-weight: 700;
    overflow: hidden;
    line-height: 50px;
    text-shadow: none;
    text-align: center;
    position: absolute;
    background: transparent;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.6);
    -webkit-box-shadow: none;
    box-shadow: none;
    -webkit-border-radius: 0;
    border-radius: 0;
    -webkit-transition: all 0.6s cubic-bezier(0.22, 0.81, 0.01, 0.99);
    transition: all 0.6s cubic-bezier(0.22, 0.81, 0.01, 0.99);
  }

  .product_img_slide .carousel-control-next {
    top: 58%;
    left: auto;
    right: 25px;
    opacity: 1;
    width: 50px;
    bottom: auto;
    height: 50px;
    font-size: 50px;
    cursor: pointer;
    font-weight: 700;
    overflow: hidden;
    line-height: 50px;
    text-shadow: none;
    text-align: center;
    position: absolute;
    background: transparent;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.6);
    -webkit-box-shadow: none;
    box-shadow: none;
    -webkit-border-radius: 0;
    border-radius: 0;
    -webkit-transition: all 0.6s cubic-bezier(0.22, 0.81, 0.01, 0.99);
    transition: all 0.6s cubic-bezier(0.22, 0.81, 0.01, 0.99);
  }

  .product_img_slide .carousel-control-next:hover, .product_img_slide .carousel-control-prev:hover {
    color: #c13c3d;
    background: transparent;
  }

这是一个轮播小部件,它是 yii\bootstrap5\Carousel 的扩展,用于显示图片缩略图作为轮播的指示器。

这是小部件代码。

<?php
namespace app\widgets;
use Yii;
use yii\bootstrap5\Html;

class Carousel extends \yii\bootstrap5\Carousel
{
    public $thumbnails = [];

    public function init()
    {
        parent::init();     
        Html::addCssClass($this->options, ['data-bs-ride' => 'carousel']);
        if ($this->crossfade) {
            Html::addCssClass($this->options, ['animation' => 'carousel-fade']);
        }
    }

    public function renderIndicators(): string
    {
        if ($this->showIndicators === false){
            return '';
        }
        $indicators = [];
        for ($i = 0, $count = count($this->items); $i < $count; $i++){
            $options = [
                'data' => [
                    'bs-target' => '#' . $this->options['id'],
                    'bs-slide-to' => $i
                ],
                'type' => 'button',
                'thumb' => $this->thumbnails[$i]['thumb']
            ];
            if ($i === 0){
                Html::addCssClass($options, ['activate' => 'active']);
                $options['aria']['current'] = 'true';
            }       

             $indicators[] = Html::tag('li',Html::img($options['thumb']), $options);
        }
        return Html::tag('ol', implode("\n", $indicators), ['class' => ['carousel-indicators']]);
    } }

您可以在您的视图文件中使用以上小部件,如下所示

    <?php  
$indicators = [
   '0' =>[ 'thumb' => "https://placehold.co/150X150?text=A"],
   '1' => ['thumb' => 'https://placehold.co/150X150?text=B'],
   '2' => [ 'thumb' => 'https://placehold.co/150X150?text=C']
];
$items = [
    [ 'content' =>Html::img('https://live.staticflickr.com/8333/8417172316_c44629715e_w.jpg')],
    [ 'content' =>Html::img('https://live.staticflickr.com/3812/9428789546_3a6ba98c49_w.jpg')],
    [ 'content' =>Html::img('https://live.staticflickr.com/8514/8468174902_a8b505a063_w.jpg')]   
];

echo Carousel::widget([
    'items' => 
        $items,
     'thumbnails'  => $indicators,
     'options' => [       
          'data-interval' => 3, 'data-bs-ride' => 'scroll','class' => 'carousel product_img_slide',
      ],

]);
]]>
0
[wiki] 如何在菜单中添加下拉语言选择器(i18n) 周六,2023 年 12 月 16 日 15:42:40 +0000 https://yiiframework.cn/wiki/2577/how-to-add-a-dropdown-language-picker-i18n-to-the-menuhttps://yiiframework.cn/wiki/2577/how-to-add-a-dropdown-language-picker-i18n-to-the-menu JQL JQL

如何在 Yii2 的导航栏菜单中添加国际化

  1. 创建所需文件
  2. 编辑 /config/web.php 文件
  3. 编辑“views”文件夹中的所有文件和任何子文件夹
  4. 创建要翻译的文本
  5. 创建一个菜单项(下拉菜单)来更改语言
  6. 可选项目

Yii 附带了国际化 (i18n)“开箱即用”。手册中有关于如何配置 Yii 使用 i18n 的说明,但关于如何将 i18n 完全集成到引导菜单中却很少有信息。本文档旨在解决这一问题。

Screenshot_i18n_s.png

GitHub 存储库还包含语言标志、一些国家标志、语言代码列表及其语言名称以及 Yii“开箱即用”识别的语言列表。YouTube 上很快就会发布一个视频。

确保您的系统已设置为使用 i18n。来自 Yii2 手册

Yii 使用PHP intl扩展来提供其大部分 I18N 功能,例如yii\i18n\Formatter类的日期和数字格式以及使用yii\i18n\MessageFormatter的消息格式。这两个类在未安装 intl 扩展时提供回退机制。但是,回退实现仅适用于英语目标语言。因此,强烈建议您在需要 I18N 时安装intl

创建所需文件

首先,您需要创建一个配置文件。

决定将配置文件存储在何处(例如,在./messages/目录中,文件名create_i18n.php)。在项目中创建该目录,然后从项目的根目录在终端(Windows:CMD)中执行以下命令

./yii message/config-template ./messages/create_i18n.php

或者,为了更细致的控制

./yii message/config --languages=en-US --sourcePath=@app --messagePath=messages ./messages/create_i18n.php

在新建的文件中,更改(或创建)要翻译的语言数组

  // array, required, list of language codes that the extracted messages
  // should be translated to. For example, ['zh-CN', 'de'].
  'languages' => [
    'en-US',
    'fr',
    'pt'
  ],

如果需要,更改create_i18n.php中的根目录以指向 messages 目录 - 默认值为messages。请注意,如果以上文件位于 messages 目录中(建议),则不要更改此'messagePath' => __DIR__,。如果您将messages的目录更改为,例如,/config/(不建议),您可以使用以下命令

  // Root directory containing message translations.
  'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'config',

编辑完您需要的语言后,创建的文件应该类似于以下内容

<?php

return [
  // string, required, root directory of all source files
  'sourcePath' => __DIR__ . DIRECTORY_SEPARATOR . '..',
  // array, required, list of language codes (in alphabetical order) that the extracted messages
  // should be translated to. For example, ['zh-CN', 'de'].
  'languages' => [
    // to localise a particular language use the language code followed by the dialect in CAPS
    'en-US',  // USA English
    'es',
    'fr',
    'it',
    'pt',
  ],
  /* 'languages' => [
    'af', 'ar', 'az', 'be', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'el', 'es', 'et', 'fa', 'fi', 'fr', 'he', 'hi',
    'pt-BR', 'ro', 'hr', 'hu', 'hy', 'id', 'it', 'ja', 'ka', 'kk', 'ko', 'kz', 'lt', 'lv', 'ms', 'nb-NO', 'nl',
    'pl', 'pt', 'ru', 'sk', 'sl', 'sr', 'sr-Latn', 'sv', 'tg', 'th', 'tr', 'uk', 'uz', 'uz-Cy', 'vi', 'zh-CN',
    'zh-TW'
    ], */
  // string, the name of the function for translating messages.
  // Defaults to 'Yii::t'. This is used as a mark to find the messages to be
  // translated. You may use a string for single function name or an array for
  // multiple function names.
  'translator' => ['\Yii::t', 'Yii::t'],
  // boolean, whether to sort messages by keys when merging new messages
  // with the existing ones. Defaults to false, which means the new (untranslated)
  // messages will be separated from the old (translated) ones.
  'sort' => false,
  // boolean, whether to remove messages that no longer appear in the source code.
  // Defaults to false, which means these messages will NOT be removed.
  'removeUnused' => false,
  // boolean, whether to mark messages that no longer appear in the source code.
  // Defaults to true, which means each of these messages will be enclosed with a pair of '@@' marks.
  'markUnused' => true,
  // array, list of patterns that specify which files (not directories) should be processed.
  // If empty or not set, all files will be processed.
  // See helpers/FileHelper::findFiles() for pattern matching rules.
  // If a file/directory matches both a pattern in "only" and "except", it will NOT be processed.
  'only' => ['*.php'],
  // array, list of patterns that specify which files/directories should NOT be processed.
  // If empty or not set, all files/directories will be processed.
  // See helpers/FileHelper::findFiles() for pattern matching rules.
  // If a file/directory matches both a pattern in "only" and "except", it will NOT be processed.
  'except' => [
    '.*',
    '/.*',
    '/messages',
    '/migrations',
    '/tests',
    '/runtime',
    '/vendor',
    '/BaseYii.php',
  ],
  // 'php' output format is for saving messages to php files.
  'format' => 'php',
  // Root directory containing message translations.
  'messagePath' => __DIR__,
  // boolean, whether the message file should be overwritten with the merged messages
  'overwrite' => true,
  /*
    // File header used in generated messages files
    'phpFileHeader' => '',
    // PHPDoc used for array of messages with generated messages files
    'phpDocBlock' => null,
   */

  /*
    // Message categories to ignore
    'ignoreCategories' => [
    'yii',
    ],
   */

  /*
    // 'db' output format is for saving messages to database.
    'format' => 'db',
    // Connection component to use. Optional.
    'db' => 'db',
    // Custom source message table. Optional.
    // 'sourceMessageTable' => '{{%source_message}}',
    // Custom name for translation message table. Optional.
    // 'messageTable' => '{{%message}}',
   */

  /*
    // 'po' output format is for saving messages to gettext po files.
    'format' => 'po',
    // Root directory containing message translations.
    'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages',
    // Name of the file that will be used for translations.
    'catalog' => 'messages',
    // boolean, whether the message file should be overwritten with the merged messages
    'overwrite' => true,
   */
];

编辑/config/web.php文件

web.php文件中,在'id' => 'basic',下方添加

  'language' => 'en',
  'sourceLanguage' => 'en',

注意:您应始终使用'sourceLanguage' => 'en',因为通常从英语翻译成其他语言更简单且更便宜。如果没有设置sourceLanguage,则默认为'en'

将以下内容添加到'components' => [...]部分

    'i18n' => [
      'translations' => [
        'app*' => [
          'class' => 'yii\i18n\PhpMessageSource',  // Using text files (usually faster) for the translations
          //'basePath' => '@app/messages',  // Uncomment and change this if your folder is not called 'messages'
          'sourceLanguage' => 'en',
          'fileMap' => [
            'app' => 'app.php',
            'app/error' => 'error.php',
          ],
          //  Comment out in production version
          //  'on missingTranslation' => ['app\components\TranslationEventHandler', 'handleMissingTranslation'],
        ],
      ],
    ],

编辑“views”文件夹中的所有文件和任何子文件夹

现在告诉 Yii 您要在视图文件中翻译哪些文本。这是通过在代码中添加Yii::t('app', '要翻译的文本')来完成的。

例如,在/views/layouts/main.php中,更改菜单标签,如下所示

    'items' => [
          //  ['label' => 'Home', 'url' => ['/site/index']],	// Orignal code
          ['label' => Yii::t('app', 'Home'), 'url' => ['/site/index']],
          ['label' => Yii::t('app', 'About'), 'url' => ['/site/about']],
          ['label' => Yii::t('app', 'Contact'), 'url' => ['/site/contact']],
          Yii::$app->user->isGuest ? ['label' => Yii::t('app', 'Login'), 'url' => ['/site/login']] : '<li class="nav-item">'
            . Html::beginForm(['/site/logout'])
            . Html::submitButton(
             // 'Logout (' . Yii::$app->user->identity->username . ')', // change this line as well to the following:
              Yii::t('app', 'Logout ({username})'), ['username' => Yii::$app->user->identity->username]),
              ['class' => 'nav-link btn btn-link logout']
            )
            . Html::endForm()
            . '</li>',
        ],

创建要翻译的文本

要创建翻译文件,请从项目的根目录在终端中运行以下命令

./yii message ./messages/create_i18n.php

现在,获取已翻译的消息。例如,在法语/messages/fr/app.php

  'Home' => 'Accueil',
  'About' => 'À propos',
  ...

创建一个菜单项(下拉菜单)来更改语言

这需要几个步骤。

1. 创建所需语言的数组

每种语言都需要一个key和一个name

key是 ICU 语言代码 ISO 639.1(小写)(可选的国家/地区代码 ISO 3166 大写)例如

法语:fr 或 法语加拿大:fr-CA

葡萄牙语:pt 或 葡萄牙语巴西:pt-BR

name是该语言的名称(用该语言表示)。例如,法语:'Français',日语:'日本の'这一点很重要,因为用户可能不理解浏览器的当前语言。

/config/params.php中,创建一个名为languages的数组,其中包含所需的语言。例如

  /* 		List of languages and their codes
   *
   * 		format:
   * 		'Language Code' => 'Language Name',
   * 		e.g.
   * 		'fr' => 'Français',
   *
   * 		please use alphabetical order of language code
   * 		Use the language name in the "user's" Language
   *            e.g.
   *            'ja' => '日本の',
   */
  'languages' => [
//    'da' => 'Danske',
//    'de' => 'Deutsche',
//    'en' => 'English', // NOT REQUIRED the sourceLanguage (i.e. the default)
    'en-GB' => 'British English',
    'en-US' => 'American English',
    'es' => 'Español',
    'fr' => 'Français',
    'it' => 'Italiano',
//    'ja' => '日本の',  // Japanese with the word "Japanese" in Kanji
//    'nl' => 'Nederlandse',
//    'no' => 'Norsk',
//    'pl' => 'Polski',
    'pt' => 'Português',
//    'ru' => 'Русский',
//    'sw' => 'Svensk',
//    'zh' => '中国的',
  ],
2. 创建一个操作

/controllers/SiteController.php(默认控制器)中,添加一个名为actionLanguage()的“操作”。此“操作”更改语言并设置 cookie,以便浏览器“记住”页面的语言请求以及对该站点的回访。

  /**
   * Called by the ajax handler to change the language and
   * Sets a cookie based on the language selected
   *
   */
  public function actionLanguage()
  {
    $lang = Yii::$app->request->post('lang');
    // If the language "key" is not NULL and exists in the languages array in params.php, change the language and set the cookie
    if ($lang !== NULL && array_key_exists($lang, Yii::$app->params['languages']))
    {
      $expire = time() + (60 * 60 * 24 * 365); //  1 year - alter accordingly
      Yii::$app->language = $lang;
      $cookie = new yii\web\Cookie([
        'name' => 'lang',
        'value' => $lang,
        'expire' => $expire,
      ]);
      Yii::$app->getResponse()->getCookies()->add($cookie);
    }
    Yii::$app->end();
  }

请记住将方法设置为POST。在behaviors()中,在actions下方,设置'language' => ['post'],,如下所示

      'verbs' => [
        'class' => VerbFilter::class,
        'actions' => [
          'logout' => ['post'],
          'language' => ['post'],
        ],
      ],
3. 创建一个语言处理程序

确保为每个请求提供正确的语言。

/components/目录中,创建一个名为LanguageHandler.php的文件,并将以下代码添加到其中

<?php

/*
 * Copyright ©2023 JQL all rights reserved.
 * http://www.jql.co.uk
 */
/*
  Created on : 19-Nov-2023, 13:23:54
  Author     : John Lavelle
  Title      : LanguageHandler
 */

namespace app\components;

use yii\helpers\Html;

class LanguageHandler extends \yii\base\Behavior
{

	public function events()
	{
		return [\yii\web\Application::EVENT_BEFORE_REQUEST => 'handleBeginRequest'];
	}

	public function handleBeginRequest($event)
	{
		if (\Yii::$app->getRequest()->getCookies()->has('lang') && array_key_exists(\Yii::$app->getRequest()->getCookies()->getValue('lang'), \Yii::$app->params['languages']))
		{
      //  Get the language from the cookie if set
			\Yii::$app->language = \Yii::$app->getRequest()->getCookies()->getValue('lang');
		}
		else
		{
			//	Use the browser language - note: some systems use an underscore, if used, change it to a hyphen
			\Yii::$app->language = str_replace('_', '-', HTML::encode(locale_accept_from_http($_SERVER['HTTP_ACCEPT_LANGUAGE'])));
		}
	}

}

/* End of file LanguageHandler.php */
/* Location: ./components/LanguageHandler.php */
4. 从/config/web.php调用LanguageHandler.php

通过将以下内容添加到'params' => $params,的正上方或正下方,从/config/web.php“调用”LanguageHandler.php文件

  //	Update the language on selection
  'as beforeRequest' => [
    'class' => 'app\components\LanguageHandler',
  ],
5. 将语言菜单项添加到/views/layouts/main.php

main.php使用 Bootstrap 来创建菜单。需要在菜单中添加一个项目(下拉菜单),以允许用户选择语言。

use yii\helpers\Url;添加到main.php的“使用”部分。

echo Nav::widget([...])的正上方添加以下代码

// Get the languages and their keys, also the current route
      foreach (Yii::$app->params['languages'] as $key => $language)
      {
        $items[] = [
          'label' => $language, // Language name in it's language - already translated
          'url' => Url::to(['site/index']), // Route
          'linkOptions' => ['id' => $key, 'class' => 'language'], // The language "key"
        ];
      }

在以下部分中

echo Nav::widget([...])`

位于

'options' => ['class' => 'navbar-nav ms-auto'], // ms-auto aligns the menu right`

'items' => [...]

之间,添加

'encodeLabels' => false, // Required to enter HTML into the labels

如下所示

      echo Nav::widget([
        'options' => ['class' => 'navbar-nav ms-auto'], // ms-auto aligns the menu right
        'encodeLabels' => false, // Required to enter HTML into the labels
        'items' => [
          ['label' => Yii::t('app', 'Home'), 'url' => ['/site/index']],
        ...

现在添加下拉菜单。它可以放置在'items' => [...]中的任何位置。

// Dropdown Nav Menu: https://yiiframework.cn/doc/api/2.0/yii-widgets-menu
        [
          'label' => Yii::t('app', 'Language')),
          'url' => ['#'],
          'options' => ['class' => 'language', 'id' => 'languageTop'],
          'encodeLabels' => false, // Optional but required to enter HTML into the labels for images
          'items' => $items, // add the languages into the Dropdown
        ],

main.php中用于导航栏的代码应该类似于以下内容

      NavBar::begin([
        'brandLabel' => Yii::$app->name,  // set in /config/web.php
        'brandUrl' => Yii::$app->homeUrl,
        'options' => ['class' => 'navbar-expand-md navbar-dark bg-dark fixed-top']
      ]);
      // Get the languages and their keys, also the current route
      foreach (Yii::$app->params['languages'] as $key => $language)
      {
        $items[] = [
          'label' => $language, // Language name in it's language
          'url' => Url::to(['site/index']), // Current route so the page refreshes
          'linkOptions' => ['id' => $key, 'class' => 'language'], // The language key
        ];
      }
      echo Nav::widget([
        'options' => ['class' => 'navbar-nav ms-auto'], // ms-auto aligns the menu right
        'encodeLabels' => false, // Required to enter HTML into the labels
        'items' => [
          ['label' => Yii::t('app', 'Home'), 'url' => ['/site/index']],
          ['label' => Yii::t('app', 'About'), 'url' => ['/site/about']],
          ['label' => Yii::t('app', 'Contact'), 'url' => ['/site/contact']],
          // Dropdown Nav Menu: https://yiiframework.cn/doc/api/2.0/yii-widgets-menu
          [
            'label' => Yii::t('app', 'Language') ,
            'url' => ['#'],
            'options' => ['class' => 'language', 'id' => 'languageTop'],
            'encodeLabels' => false, // Required to enter HTML into the labels
            'items' => $items, // add the languages into the Dropdown
          ],
          Yii::$app->user->isGuest ? ['label' => Yii::t('app', 'Login'), 'url' => ['/site/login']] : '<li class="nav-item">'
            . Html::beginForm(['/site/logout'])
            . Html::submitButton(
//              'Logout (' . Yii::$app->user->identity->username . ')',
              Yii::t('app', 'Logout ({username})', ['username' => Yii::$app->user->identity->username]),
              ['class' => 'nav-link btn btn-link logout']
            )
            . Html::endForm()
            . '</li>',
        ],
      ]);
      NavBar::end();

如果需要在语言名称旁边添加语言标志或图片,请参见本文档末尾的“可选项目”。

6. 使用 Ajax 调用触发语言更改

要调用语言操作actionLanguage(),请在 JavaScript 文件中进行 Ajax 调用。

/web/js/中创建一个名为language.js的文件。

将以下代码添加到文件中

/*
 * Copyright ©2023 JQL all rights reserved.
 * http://www.jql.co.uk
 */

/**
 * Set the language
 *
 * @returns {undefined}
 */
$(function () {
  $(document).on('click', '.language', function (event) {
    event.preventDefault();
    let lang = $(this).attr('id');  // Get the language key
    /* if not the top level, set the language and reload the page */
    if (lang !== 'languageTop') {
      $.post(document.location.origin + '/site/language', {'lang': lang}, function (data) {
        location.reload(true);
      });
    }
  });
});

要将 JavaScript 文件添加到资产中,请更改项目目录中的/assets/AppAsset.php。在public $js = []中添加'js/language.js',如下所示

     public $js = [
       'js/language.js',
     ];

国际化现在应该在您的项目中生效。

可选项目

以下内容是可选的,但可能对您和/或用户有所帮助。

1. 检查翻译

Yii 可以检查是否为Yii::t('app', '要翻译的文本')块中的特定文本存在翻译。

有两个步骤

A./config/web.php中取消注释以下行

  //  'on missingTranslation' => ['app\components\TranslationEventHandler', 'handleMissingTranslation'],

B. 创建一个 TranslationEventHandler

/components/中创建一个名为TranslationEventHandler.php的文件,并将以下代码添加到其中


<?php

/**
 * TranslationEventHandler
 *
 * @copyright © 2023, John Lavelle  Created on : 14 Nov 2023, 16:05:32
 *
 *
 * Author     : John Lavelle
 * Title      : TranslationEventHandler
 */
// Change the Namespace (app, frontend, backend, console etc.) if necessary (default in Yii Basic is "app").

namespace app\components;

use yii\i18n\MissingTranslationEvent;

/**
 * TranslationEventHandler
 *
 *
 * @author John Lavelle
 * @since 1.0 // Update version number
 */
class TranslationEventHandler
{

  /**
   * Adds a message to missing translations in Development Environment only
   *
   * @param MissingTranslationEvent $event
   */
  public static function handleMissingTranslation(MissingTranslationEvent $event)
  {
    // Only check in the development environment
    if (YII_ENV_DEV)
    {
      $event->translatedMessage = "@MISSING: {$event->category}.{$event->message} FOR LANGUAGE {$event->language} @";
    }
  }
}

如果存在缺少的翻译,则文本将替换为类似于以下文本的消息

@MISSING: app.Logout (John) FOR LANGUAGE fr @

这里 Yii 发现没有法语翻译

Yii::t('app', 'Logout ({username})', ['username' => Yii::$app->user->identity->username]),
2. 在下拉菜单中添加语言标志

这非常有用且推荐使用,因为它可以帮助用户找到正确的语言。这需要几个步骤。

a. 创建标志图片。

图片的宽度应为 25 像素,高度应为 15 像素。图片必须与 params.php 中语言数组中的语言键具有相同的名称。例如:fr.pngen-US.png。如果图片不是“.png”类型,请将下面部分b.中的代码更改为正确的文件扩展名。

将图片放在/web/images/flags/目录中。

b. 更改/views/layouts/main.php中的代码,使“导航栏”的代码如下所示

<header id="header">
      <?php
      NavBar::begin([
        'brandLabel' => Yii::$app->name,
        'brandUrl' => Yii::$app->homeUrl,
        'options' => ['class' => 'navbar-expand-md navbar-dark bg-dark fixed-top']
      ]);
      // Get the languages and their keys, also the current route
      foreach (Yii::$app->params['languages'] as $key => $language)
      {
        $items[] = [
	// Display the image before the language name
          'label' => Html::img('/images/flags/' . $key . '.png', ['alt' => 'flag ' . $language, 'class' => 'inline-block align-middle', 'title' => $language,]) . ' ' . $language, // Language name in it's language
          'url' => Url::to(['site/index']), // Route
          'linkOptions' => ['id' => $key, 'class' => 'language'], // The language key
        ];
      }
      echo Nav::widget([
        'options' => ['class' => 'navbar-nav ms-auto'], // ms-auto aligns the menu right
        'encodeLabels' => false, // Required to enter HTML into the labels
        'items' => [
          ['label' => Yii::t('app', 'Home'), 'url' => ['/site/index']],
          ['label' => Yii::t('app', 'About'), 'url' => ['/site/about']],
          ['label' => Yii::t('app', 'Contact'), 'url' => ['/site/contact']],
          // Dropdown Nav Menu: https://yiiframework.cn/doc/api/2.0/yii-widgets-menu
          [
	  // Display the current language "flag" after the Dropdown title (before the caret)
            'label' => Yii::t('app', 'Language') . ' ' . Html::img('@web/images/flags/' . Yii::$app->language . '.png', ['class' => 'inline-block align-middle', 'title' => Yii::$app->language]),
            'url' => ['#'],
            'options' => ['class' => 'language', 'id' => 'languageTop'],
            'encodeLabels' => false, // Required to enter HTML into the labels
            'items' => $items, // add the languages into the Dropdown
          ],
          Yii::$app->user->isGuest ? ['label' => Yii::t('app', 'Login'), 'url' => ['/site/login']] : '<li class="nav-item">'
            . Html::beginForm(['/site/logout'])
            . Html::submitButton(
//              'Logout (' . Yii::$app->user->identity->username . ')',
              Yii::t('app', 'Logout ({username})', ['username' => Yii::$app->user->identity->username]),
              ['class' => 'nav-link btn btn-link logout']
            )
            . Html::endForm()
            . '</li>',
        ],
      ]);
      NavBar::end();
      ?>
    </header>

就这样!享受吧…

要了解更多信息,请参阅

GitHub 上的 i18ntutorial

Yii2 国际化教程

PHP intl 扩展

如果您使用此代码,请按如下方式说明

国际化 (i18n) 菜单代码由 JQL 提供,https://visualaccounts.co.uk ©2023 JQL

许可证 (BSD-3-Clause 许可证)

版权声明

国际化 (i18n) 菜单代码由 JQL 提供,https://visualaccounts.co.uk ©2023 JQL 保留所有权利

在满足以下条件的情况下,允许以源代码和二进制形式重新分发和使用此软件,无论是否修改。

源代码的再分发必须保留上述版权声明、此条件列表和以下免责声明。

二进制形式的再分发必须在随分发提供的文档和/或其他材料中复制上述版权声明、此条件列表和以下免责声明。

未经事先明确书面许可,不得使用 John Lavelle、JQL、Visual Accounts 或其贡献者的名称来认可或宣传源于此软件的产品。

"所有 JQL 代码和软件(包括全球万维网页面以及其作者的页面)均按“原样”提供,不附带任何形式的保证。在法律允许的最大范围内,作者和出版商及其代理商明确声明不承担任何明示或暗示的保证,包括但不限于对适销性和特定用途适用性的暗示保证。对于代码,作者和出版商及其代理商对因使用代码直接或间接产生的任何损失或损害不承担任何责任,即使作者和/或出版商及其代理商已收到有关此类损害的可能性通知。在不限制上述内容的情况下,作者和出版商及其代理商对因使用代码造成的任何利润损失、业务中断、设备或数据损坏、运营中断或任何其他商业损害(包括但不限于直接、间接、特殊、附带、后果性或其他损害)不承担任何责任。"

]]>
0
[扩展] slideradmin 周二,2023 年 11 月 21 日 11:20:25 +0000 https://yiiframework.cn/extension/slideradminhttps://yiiframework.cn/extension/slideradmin pravi pravi

这是一个使用 yii2 基础应用程序模板创建的应用程序模板,用于演示我的扩展 slideradmin 的用法。

安装

  1. GitHub 下载仓库并将其内容解压缩到任何文件夹中。
  2. 运行 composer update 命令。
  3. 运行迁移命令:php yii migrate --migrationPath="@vendor/siripravi/yii2-slideradmin/migrations"
  4. 完成。
]]>
0
[扩展] nicksdr/nkchartjs 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/extension/nicksdr/nkchartjshttps://yiiframework.cn/extension/nicksdr/nkchartjs CarlosQS CarlosQS

nkchartjs

]]>
0
[维基] 如何使用正则表达式创建和使用验证器 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/wiki/2575/how-to-create-and-use-validator-using-regular-expressionshttps://yiiframework.cn/wiki/2575/how-to-create-and-use-validator-using-regular-expressions aayushmhu aayushmhu

有多种方法可以创建验证器,但这里我们使用正则表达式或 JavaScript 正则表达式或 RegExp 来创建验证器。在本文中,我们将看到最常用的表达式。

步骤 1: 创建一个新的验证器类,如下所示或 Validator

查看第一个示例 10 位手机号码验证。

<?php

namespace common\validators;

use yii\validators\Validator;

class MobileValidator extends Validator {

    public function validateAttribute($model, $attribute) {
        if (isset($model->$attribute) and $model->$attribute != '') {
             if (!preg_match('/^[123456789]\d{9}$/', $model->$attribute)) {
                $this->addError($model, $attribute, 'In Valid Mobile / Phone number');
            }
        }
    }

}

这里我们可以根据需要编写不同的正则表达式 `php preg_match('/^[123456789]\d{9}$/', $model->$attribute) `

步骤 2: 如何使用验证器

我希望每个人都知道如何使用验证器,但这里有一个关于如何使用它的示例。

在您的模型类中添加一个新的规则,如下所示 `php [['mobile'],\common\validators\MobileValidator::class], [['mobile'], 'string', 'max' => 10],


So It's Very Simple to use a Custom Validator.


As I Told you Earlier that i show you some more Example for Using Regular Expression  Validator Just Replace these string in preg_match.

1. Aadhar Number Validator
```php
preg_match('/^[2-9]{1}[0-9]{3}[0-9]{4}[0-9]{4}$/', $model->$attribute)
  1. 银行账户号码验证器 `php preg_match("/^[0-9]{9,18}+$/", $model->$attribute) `

  2. 银行 IFSC 代码验证器 `php preg_match("/^[A-Z]{4}0[A-Z0-9]{6}$/", $model->$attribute) `

  3. 身份证号码验证器 `php preg_match('/^([a-zA-Z]){5}([0-9]){4}([a-zA-Z]){1}?$/', $model->$attribute) `

  4. 邮政编码验证器 `php preg_match('/^[0-9]{6}+$/', $model->$attribute) `

  5. GSTIN 验证器 `php preg_match("/^([0][1-9]|[1-2][0-9]|[3][0-5])([a-zA-Z]{5}[0-9]{4}[a-zA-Z]{1}[1-9a-zA-Z]{1}[zZ]{1}[0-9a-zA-Z]{1})+$/", $model->$attribute) `

这是其他类型的自定义验证器。

  1. 500 字字符串验证器
<?php

namespace common\validators;

use yii\validators\Validator;

/**
 * Class Word500Validator
 * @author Aayush Saini <aayushsaini9999@gmail.com>
 */
class Word500Validator extends Validator
{

    public function validateAttribute($model, $attribute)
    {
        if ($model->$attribute != '') {
            if (str_word_count($model->$attribute) > 500) {
                $this->addError($model, $attribute, $model->getAttributeLabel($attribute) . ' length can not exceeded 500 words.');
                \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
                return $model->errors;
            }
        }
    }
}

现在我假设您在阅读完这篇文章后可以根据您的需要创建任何类型的验证器。

:) 感谢阅读

]]>
0
[维基] GridView 在页脚显示列的总和。 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/wiki/2574/gridview-show-sum-of-columns-in-footerhttps://yiiframework.cn/wiki/2574/gridview-show-sum-of-columns-in-footer shivam4u shivam4u

GridView 在页脚显示列的总和 `PHP use yii\grid\DataColumn;

/**

  • 所有列值的总和
  • @author shiv / class TSumColumn extends DataColumn { public function getDataCellValue($model, $key, $index) {

     $value = parent::getDataCellValue($model, $key, $index);
     if ( is_numeric($value))
     {
         $this->footer += $value;
     }
        
     return $value;
    

    } } `

现在您必须在 GridView 中启用页脚。

echo GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'showFooter' => true,

还要更改列类。

            [
                'class' => TSumColumn::class,
                'attribute' => 'amount'
            ],

您将在网格的页脚中看到总计。如果需要,可以将其应用于多个列。

]]>
0
[维基] 将 JSON 数据转换为 HTML 表格以在页面上显示。 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/wiki/2573/convert-json-data-to-html-table-for-display-on-pagehttps://yiiframework.cn/wiki/2573/convert-json-data-to-html-table-for-display-on-page shivam4u shivam4u

我有一个调用,它可以帮助我直接在 HTML 表格中显示 json。

Json2Table::formatContent($json);

Json2Table 类的代码如下:

============================================

/**
 * Class convert Json to html table. It help view json data directly.
 * @author shiv
 *
 */
class Json2Table
{

    public static function formatContent($content, $class = 'table table-bordered')
    {
        $html = "";
        if ($content != null) {
            $arr = json_decode(strip_tags($content), true);
            
            if ($arr && is_array($arr)) {
                $html .= self::arrayToHtmlTableRecursive($arr, $class);
            }
        }
        return $html;
    }

    public static function arrayToHtmlTableRecursive($arr, $class = 'table table-bordered')
    {
        $str = "<table class='$class'><tbody>";
        foreach ($arr as $key => $val) {
            $str .= "<tr>";
            $str .= "<td>$key</td>";
            $str .= "<td>";
            if (is_array($val)) {
                if (! empty($val)) {
                    $str .= self::arrayToHtmlTableRecursive($val, $class);
                }
            } else {
                $val = nl2br($val);
                $str .= "<strong>$val</strong>";
            }
            $str .= "</td></tr>";
        }
        $str .= "</tbody></table>";
        
        return $str;
    }
}
]]>
0
[维基] 身份证号码验证器 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/wiki/2572/aadhar-number-validatorhttps://yiiframework.cn/wiki/2572/aadhar-number-validator shivam4u shivam4u

在印度有身份证号码,我们可能需要对输入的身份证号码进行验证。所以我为 yii2 创建了一个验证器。

use yii\validators\Validator;

class TAadharNumberValidator extends Validator
{

    public $regExPattern = '/^\d{4}\s\d{4}\s\d{4}$/';

    public function validateAttribute($model, $attribute)
    {
        if (preg_match($this->regExPattern, $model->$attribute)) {
            $model->addError($attribute, 'Not valid Aadhar Card Number');
        }
    }
}
]]>
0
[维基] YII2 面试问题 周三,2023 年 11 月 1 日 06:05:47 +0000 https://yiiframework.cn/wiki/2570/interview-questions-for-yii2https://yiiframework.cn/wiki/2570/interview-questions-for-yii2 aayushmhu aayushmhu

大家好,在这篇文章中,我分享了自己在 YII2 面试中最常被问到的问题。

  1. 什么是 Active Record?我们如何使用它?
  2. 什么是组件?
  3. 什么是辅助函数?
  4. 如何更新数据模型?
  5. 身份验证和授权的区别?
  6. 如何加快网站速度?
  7. 什么是 GII?您是否使用 GII 模块?
  8. YII 和 YII2 的区别是什么?
  9. 如何使用多个数据库?
  10. 如何将主题集成到网站中?
  11. 什么是 OOP?
  12. 什么是 PHP 中的 final 类?
  13. 什么是抽象类?
  14. 什么是继承?
  15. 什么是接口?
  16. 您是否了解 JavaScript 和 Jquery?
  17. 什么是 trait?
  18. 什么是引导?
  19. YII2 的高级版和基础版之间有什么区别?
  20. 如何将 YII2 用作微框架?
  21. 什么是 REST API?如何在 YII2 中编写 REST API?
  22. YII2 项目的目录结构?
  23. render、renderFile、renderPartial、renderAjax、renderContent 之间的区别?

这些是最常见的面试官可能会问您的问题,如果您要去面试。

如果有人有其他问题,请在评论中分享!

]]>
0
[维基] 如何通过 Gmail SMTP 在 Yii2 框架中发送电子邮件 周三,2021 年 8 月 4 日 13:00:37 +0000 https://yiiframework.cn/wiki/2569/how-to-send-email-via-gmail-smtp-in-yii2-frameworkhttps://yiiframework.cn/wiki/2569/how-to-send-email-via-gmail-smtp-in-yii2-framework PELock PELock
  1. Gmail 不会解封您的域名... 感谢 Google
  2. 如何向 @gmail.com 的邮箱发送电子邮件?
  3. 1. 设置辅助 @gmail.com 帐户
  4. 2. 在您的配置文件中添加自定义组件
  5. 3. 添加辅助函数
  6. 4. 使用
  7. 5. 了解限制
  8. 6. Gmail 不是您的朋友

我的一个 网站 充斥着垃圾邮件机器人,因此 Gmail 给我的邮件域名打了一个差评,我再也无法向@gmail 地址发送电子邮件了,无论是从我的电子邮件、我的系统还是我托管的其他域名和网站。

Gmail 不会解封您的域名... 感谢 Google

我确实从我的一个网站中删除了所有垃圾邮件机器人的活动,通过 Gmail 支持论坛申诉了该决定,但仍然,我被阻止与我的客户联系,他们的邮箱位于@gmail.com,而且似乎无法将域名分数更改回原来的水平。

已经快两个星期了,我的域名分数仍然在 https://postmaster.google.com/ 上保持差评

感谢 @Google :(

如何向 @gmail.com 的邮箱发送电子邮件?

因此,我不得不找到一种方法来向我的客户发送购买、许可证过期和其他通知。

我使用的是 PHP Yii2 框架,事实证明,这非常容易。

1. 设置辅助 @gmail.com 帐户

我们需要一个 @gmail.com 帐户来发送通知。有一点很重要。创建帐户后,您需要启用安全性较低应用程序访问选项。

Gmail options

它允许我们通过 Gmail SMTP 服务器发送电子邮件。

2. 在您的配置文件中添加自定义组件

在您的 Yii2 框架目录中,修改您的配置文件/common/config/Main.php(我使用的是高级主题)并包含自定义邮件组件(随意命名)

<?php
return [
	'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',

	...

	'components' => [

		'mailerGmail' => [
			'class' => 'yii\swiftmailer\Mailer',
			'viewPath' => '@common/mail',
			'useFileTransport' => false,

			'transport' => [
				'class' => 'Swift_SmtpTransport',
				'host' => 'smtp.gmail.com',
				'username' => 'gmail.helper.account',
				'password' => 'PUT-YOUR-PASSWORD-HERE',
				'port' => '587',
				'encryption' => 'tls',
			],
		],
    ],
];

3. 添加辅助函数

我在我的一个组件中添加了一个辅助函数,该组件注册为Yii::$app->Custom。它根据要发送电子邮件的域名的名称返回默认的邮件程序实例。

我还更新了代码以检测电子邮件不包含@gmail.com 字符串,但仍然使用 Gmail MX 服务器来处理电子邮件的情况。

检测是基于使用 PHP 内置函数getmxrr() 检查域名邮件服务器记录,如果失败,我会向 Google DNS 服务 API 发送远程GET 查询以检查MX 记录。

////////////////////////////////////////////////////////////////////////////////
//
// get default mailer depending on the provided email address
//
////////////////////////////////////////////////////////////////////////////////

public function getMailer($email)
{
	// detect if the email or domain is using Gmail to send emails
	if (Yii::$app->params['forwardGmail'])
	{
		// detect @gmail.com domain first
		if (str_ends_with($email, "@gmail.com"))
		{
			return Yii::$app->mailerGmail;
		}

		// extract domain name
		$parts = explode('@', $email);
		$domain = array_pop($parts);

		// check DNS using local server requests to DNS
		// if it fails query Google DNS service API (might have limits)
		if (getmxrr($domain, $mx_records))
		{
			foreach($mx_records as $record)
			{
				if (stripos($record, "google.com") !== false || stripos($record, "googlemail.com") !== false)
				{
					return Yii::$app->mailerGmail;
				}
			}

			// return default mailer (if there were records detected but NOT google)
			return Yii::$app->mailer;
		}

		// make DNS request
		$client = new Client();

		$response = $client->createRequest()
			->setMethod('GET')
			->setUrl('https://dns.google.com/resolve')
			->setData(['name' => $domain, 'type' => 'MX'])
			->setOptions([
				'timeout' => 5, // set timeout to 5 seconds for the case server is not responding
			])
			->send();

		if ($response->isOk)
		{
			$parser = new JsonParser();

			$data = $parser->parse($response);

			if ($data && array_key_exists("Answer", $data))
			{
				foreach ($data["Answer"] as $key => $value)
				{
					if (array_key_exists("name", $value) && array_key_exists("data", $value))
					{
						if (stripos($value["name"], $domain) !== false)
						{
							if (stripos($value["data"], "google.com") !== false || stripos($value["data"], "googlemail.com") !== false)
							{
								return Yii::$app->mailerGmail;
							}
						}
					}
				}
			}
		}
	}

	// return default mailer
	return Yii::$app->mailer;
}

如果域名以@gmail.com 结尾,或者域名使用的是 Gmail 邮件系统,则使用mailerGmail 实例,否则使用默认邮件组件Yii::$app->mailer

4. 使用

    /**
     * Sends an email to the specified email address using the information collected by this model.
     *
     * @return boolean whether the email was sent
     */
    public function sendEmail()
    {
		// find all active subscribers
		$message = Yii::$app->Custom->getMailer($this->email)->compose();
	
		$message->setTo([$this->email => $this->name]);
		$message->setFrom([\Yii::$app->params['supportEmail'] => "Bartosz Wójcik"]);
		$message->setSubject($this->subject);
		$message->setTextBody($this->body);
	
		$headers = $message->getSwiftMessage()->getHeaders();
	
		// message ID header (hide admin panel)
		$msgId = $headers->get('Message-ID');
		$msgId->setId(md5(time()) . '@pelock.com');
	
		$result = $message->send();
	
		return $result;
    }

5. 了解限制

这只是一个临时解决方案,您需要了解,使用这种方法无法发送批量邮件,Gmail 也会对新邮箱施加一些限制。

6. Gmail 不是您的朋友

如果您的域名落在信誉度差的范围内,似乎没有任何简单的解决办法。我在 Gmail 支持论坛上看到,有些人等待了一个多月才让 Gmail 解封了他们的域名,没有任何结果和回复。我的域名没有列在其他任何被阻止的 RBL 列表(垃圾邮件列表)中,只有 Gmail 阻止了它,但这足以理解 Google 的影响力,它可以在一瞬间摧毁您的业务,而您却没有机会修复它...

]]>
0
[维基] JWT 身份验证教程 周日,2021 年 10 月 3 日 17:59:49 +0000 https://yiiframework.cn/wiki/2568/jwt-authentication-tutorialhttps://yiiframework.cn/wiki/2568/jwt-authentication-tutorial allanbj allanbj

如何实现 JWT

  1. JWT 概念
  2. 场景
  3. 用户第一次通过 /auth/login 端点登录
  4. 令牌过期
  5. 我的笔记本电脑被盗了
  6. 为什么我们要盲目信任 JWT?
  7. 实施步骤
  8. 先决条件
  9. 逐步设置
  10. 客户端示例

JWT 概念

JWT 是 JSON Web Token 的缩写。例如,它用于代替会话来维护与 API 交互的浏览器的登录状态——因为浏览器会话容易受到 CSRF 安全问题的攻击。JWT 也比设置 OAuth 身份验证机制更简单。

该概念依赖于两个令牌

  • AccessToken - 一个短暂的 JWT(例如 5 分钟)

此令牌使用 \sizeg\jwt\Jwt::class 生成。它没有存储在服务器端,并在所有后续的 API 请求中通过Authorization 标头发送。那么如何识别用户呢?好吧,JWT 内容包含用户 ID。我们盲目地信任此值。

  • RefreshToken - 一个长时间存在的令牌,存储在数据库中

此令牌仅在登录时生成,并存储在user_refresh_token表中。用户在数据库中可能有多个 RefreshToken。

场景

用户第一次通过/auth/login端点登录:

在我们的actionLogin()方法中,如果凭据正确,会发生两件事。

  • JWT AccessToken 被生成并通过 JSON 发送回来。它没有存储在任何服务器端,并且包含用户 ID(已编码)。
  • RefreshToken 被生成并存储在数据库中。它不会作为 JSON 发送回来,而是作为httpOnly cookie 发送,仅限于/auth/refresh-token路径。

JWT 存储在浏览器的localStorage中,并且从现在开始必须在所有请求中发送。RefreshToken 在您的 cookie 中,但不能通过 Javascript 读取/访问/篡改(因为它 是httpOnly)。

令牌过期:

一段时间后,JWT 最终会过期。在这种情况下,您的 API 必须返回401 - Unauthorized。在您的应用程序的 HTTP 客户端(例如 Axios)中,添加一个拦截器,它检测 401 状态,将失败的请求存储在一个队列中,并调用/auth/refresh-token端点。

调用时,此端点将通过 cookie 接收 RefreshToken。然后,您必须在您的表中检查这是否是一个有效的 RefreshToken,关联的用户 ID 是谁,生成一个新的 JWT 并将其作为 JSON 发送回来。

您的 HTTP 客户端必须使用这个新的 JWT,将其替换在localStorage中,然后循环遍历请求队列并重放所有失败的请求。

我的笔记本电脑被盗:

如果您设置了/auth/sessions端点,它返回所有当前用户的 RefreshToken,那么您可以显示所有已连接设备的表格。

然后,您可以允许用户删除一行(即从表中 DELETE 某个 RefreshToken)。当受损令牌过期(例如 5 分钟后)并且尝试更新时,它将失败。这就是为什么我们希望 JWT 的生命周期非常短的原因。

为什么我们盲目地信任 JWT?

这是 JWT 的设计目的。它足够安全,可以被信任。在大型设置(例如 Google)中,身份验证由一个单独的身份验证服务器处理。它负责接受登录/密码以换取令牌。

后来,例如在 Gmail 中,根本不会执行身份验证。Google 读取您的 JWT 并允许您访问您的电子邮件,前提是您的 JWT 没有失效。如果它失效了,您将被重定向到身份验证服务器。

这就是为什么 Google 身份验证在一段时间前发生故障时,一些用户能够毫无问题地使用 Gmail,而其他用户则根本无法连接 - JWT 仍然有效,而其他用户则拥有过期的 JWT。

实现步骤

先决条件

  • 已安装 Yii2
  • 需要启用https的站点才能使 HttpOnly cookie 跨站点工作
  • 一个用于存储 RefreshToken 的数据库表
CREATE TABLE `user_refresh_tokens` (
	`user_refresh_tokenID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	`urf_userID` INT(10) UNSIGNED NOT NULL,
	`urf_token` VARCHAR(1000) NOT NULL,
	`urf_ip` VARCHAR(50) NOT NULL,
	`urf_user_agent` VARCHAR(1000) NOT NULL,
	`urf_created` DATETIME NOT NULL COMMENT 'UTC',
	PRIMARY KEY (`user_refresh_tokenID`)
)
COMMENT='For JWT authentication process';
  • 安装包: composer require sizeg/yii2-jwt
  • 对于路由登录/注销/刷新等,我们将使用名为AuthController.php的控制器。您可以随意命名它。

分步设置

  • user_refresh_tokens表创建一个 ActiveRecord 模型。我们将使用类名app\models\UserRefreshToken

  • 在所有控制器中禁用CSRF验证

添加此属性:public $enableCsrfValidation = false;

  • /config/params.php中添加 JWT 参数
'jwt' => [
	'issuer' => 'https://api.example.com',  //name of your project (for information only)
	'audience' => 'https://frontend.example.com',  //description of the audience, eg. the website using the authentication (for info only)
	'id' => 'UNIQUE-JWT-IDENTIFIER',  //a unique identifier for the JWT, typically a random string
	'expire' => 300,  //the short-lived JWT token is here set to expire after 5 min.
],
  • /components中添加JwtValidationData类,它使用我们刚刚设置的参数
<?php
namespace app\components;

use Yii;

class JwtValidationData extends \sizeg\jwt\JwtValidationData {
	/**
	 * @inheritdoc
	 */
	public function init() {
		$jwtParams = Yii::$app->params['jwt'];
		$this->validationData->setIssuer($jwtParams['issuer']);
		$this->validationData->setAudience($jwtParams['audience']);
		$this->validationData->setId($jwtParams['id']);

		parent::init();
	}
}
  • /config/web.php的配置中添加组件,用于初始化 JWT 身份验证
	$config = [
		'components' => [
			...
			'jwt' => [
				'class' => \sizeg\jwt\Jwt::class,
				'key' => 'SECRET-KEY',  //typically a long random string
				'jwtValidationData' => \app\components\JwtValidationData::class,
			],
			...
		],
	];
  • 将验证器行为添加到您的控制器
    • 对于AuthController.php,我们必须排除不需要经过身份验证的操作,例如loginrefresh-tokenoptions(当浏览器发送跨站点 OPTIONS 请求时)。
	public function behaviors() {
    	$behaviors = parent::behaviors();

		$behaviors['authenticator'] = [
			'class' => \sizeg\jwt\JwtHttpBearerAuth::class,
			'except' => [
				'login',
				'refresh-token',
				'options',
			],
		];

		return $behaviors;
	}
  • generateJwt()generateRefreshToken()方法添加到AuthController.php中。我们将在登录/刷新令牌操作中使用它们。如果用户模型不同,请调整类名。
	private function generateJwt(\app\models\User $user) {
		$jwt = Yii::$app->jwt;
		$signer = $jwt->getSigner('HS256');
		$key = $jwt->getKey();
		$time = time();

		$jwtParams = Yii::$app->params['jwt'];

		return $jwt->getBuilder()
			->issuedBy($jwtParams['issuer'])
			->permittedFor($jwtParams['audience'])
			->identifiedBy($jwtParams['id'], true)
			->issuedAt($time)
			->expiresAt($time + $jwtParams['expire'])
			->withClaim('uid', $user->userID)
			->getToken($signer, $key);
	}

	/**
	 * @throws yii\base\Exception
	 */
	private function generateRefreshToken(\app\models\User $user, \app\models\User $impersonator = null): \app\models\UserRefreshToken {
		$refreshToken = Yii::$app->security->generateRandomString(200);

		// TODO: Don't always regenerate - you could reuse existing one if user already has one with same IP and user agent
		$userRefreshToken = new \app\models\UserRefreshToken([
			'urf_userID' => $user->id,
			'urf_token' => $refreshToken,
			'urf_ip' => Yii::$app->request->userIP,
			'urf_user_agent' => Yii::$app->request->userAgent,
			'urf_created' => gmdate('Y-m-d H:i:s'),
		]);
		if (!$userRefreshToken->save()) {
			throw new \yii\web\ServerErrorHttpException('Failed to save the refresh token: '. $userRefreshToken->getErrorSummary(true));
		}

		// Send the refresh-token to the user in a HttpOnly cookie that Javascript can never read and that's limited by path
		Yii::$app->response->cookies->add(new \yii\web\Cookie([
			'name' => 'refresh-token',
			'value' => $refreshToken,
			'httpOnly' => true,
			'sameSite' => 'none',
			'secure' => true,
			'path' => '/v1/auth/refresh-token',  //endpoint URI for renewing the JWT token using this refresh-token, or deleting refresh-token
		]));

		return $userRefreshToken;
	}
  • 将登录操作添加到AuthController.php
	public function actionLogin() {
		$model = new \app\models\LoginForm();
		if ($model->load(Yii::$app->request->getBodyParams()) && $model->login()) {
			$user = Yii::$app->user->identity;

			$token = $this->generateJwt($user);

			$this->generateRefreshToken($user);

			return [
				'user' => $user,
				'token' => (string) $token,
			];
		} else {
			return $model->getFirstErrors();
		}
	}
  • 将刷新令牌操作添加到AuthController.php中。当 JWT 过期时,调用POST /auth/refresh-token,当用户请求注销时,调用DELETE /auth/refresh-token(然后从客户端的localStorage中删除 JWT 令牌)。
	public function actionRefreshToken() {
		$refreshToken = Yii::$app->request->cookies->getValue('refresh-token', false);
		if (!$refreshToken) {
			return new \yii\web\UnauthorizedHttpException('No refresh token found.');
		}

		$userRefreshToken = \app\models\UserRefreshToken::findOne(['urf_token' => $refreshToken]);

		if (Yii::$app->request->getMethod() == 'POST') {
			// Getting new JWT after it has expired
			if (!$userRefreshToken) {
				return new \yii\web\UnauthorizedHttpException('The refresh token no longer exists.');
			}

			$user = \app\models\User::find()  //adapt this to your needs
				->where(['userID' => $userRefreshToken->urf_userID])
				->andWhere(['not', ['usr_status' => 'inactive']])
				->one();
			if (!$user) {
				$userRefreshToken->delete();
				return new \yii\web\UnauthorizedHttpException('The user is inactive.');
			}

			$token = $this->generateJwt($user);

			return [
				'status' => 'ok',
				'token' => (string) $token,
			];

		} elseif (Yii::$app->request->getMethod() == 'DELETE') {
			// Logging out
			if ($userRefreshToken && !$userRefreshToken->delete()) {
				return new \yii\web\ServerErrorHttpException('Failed to delete the refresh token.');
			}

			return ['status' => 'ok'];
		} else {
			return new \yii\web\UnauthorizedHttpException('The user is inactive.');
		}
	}
  • 调整用户模型中的findIdentityByAccessToken(),以通过 JWT 中的 uid 声明查找经过身份验证的用户
	public static function findIdentityByAccessToken($token, $type = null) {
		return static::find()
			->where(['userID' => (string) $token->getClaim('uid') ])
			->andWhere(['<>', 'usr_status', 'inactive'])  //adapt this to your needs
			->one();
	}
  • 还要记住,当密码更改时,例如在用户模型的afterSave()中,清除用户的全部 RefreshToken。
	public function afterSave($isInsert, $changedOldAttributes) {
		// Purge the user tokens when the password is changed
		if (array_key_exists('usr_password', $changedOldAttributes)) {
			\app\models\UserRefreshToken::deleteAll(['urf_userID' => $this->userID]);
		}

		return parent::afterSave($isInsert, $changedOldAttributes);
	}
  • 制作一个页面,用户可以在其中删除他的 RefreshToken。列出属于给定用户的user_refresh_tokens中的记录,并允许他删除他选择记录。

客户端示例

Axios 拦截器(使用 React Redux???)


let isRefreshing = false;
let refreshSubscribers: QueuedApiCall[] = [];
const subscribeTokenRefresh = (cb: QueuedApiCall) =>
  refreshSubscribers.push(cb);

const onRefreshed = (token: string) => {
  console.log("refreshing ", refreshSubscribers.length, " subscribers");
  refreshSubscribers.map(cb => cb(token));
  refreshSubscribers = [];
};

api.interceptors.response.use(undefined,
  error => {
    const status = error.response ? error.response.status : false;
    const originalRequest = error.config;

    if (error.config.url === '/auth/refresh-token') {
      console.log('REDIRECT TO LOGIN');
      store.dispatch("logout").then(() => {
          isRefreshing = false;
      });
    }

    if (status === API_STATUS_UNAUTHORIZED) {


      if (!isRefreshing) {
        isRefreshing = true;
        console.log('dispatching refresh');
        store.dispatch("refreshToken").then(newToken => {
          isRefreshing = false;
          onRefreshed(newToken);
        }).catch(() => {
          isRefreshing = false;
        });
      }

      return new Promise(resolve => {
        subscribeTokenRefresh(token => {
          // replace the expired token and retry
          originalRequest.headers["Authorization"] = "Bearer " + token;
          resolve(axios(originalRequest));
        });
      });
    }
    return Promise.reject(error);


  }
);

感谢Mehdi Achour为本教程的大部分资料提供帮助。

]]>
0
[wiki] Yii v2 代码片段指南 III Mon, 17 Jul 2023 11:37:41 +0000 https://yiiframework.cn/wiki/2567/yii-v2-snippet-guide-iiihttps://yiiframework.cn/wiki/2567/yii-v2-snippet-guide-iii rackycz rackycz
  1. 我的文章
  2. 切换语言和 URL 中的语言
  3. 搜索和替换
  4. 虚拟化 - Vagrant 和 Docker - 为什么以及如何
  5. 在 Vagrant 中运行 Yii 项目。(简化版本)
  6. 在 Docker 中运行 Yii 项目(更新:下面添加了 xDebug!)
  7. 在 Docker 中启用 xDebug,yii 演示应用程序
  8. Docker - 自定义 php.ini
  9. 如何进入 Docker 的 bash(cli,命令行)
  10. AdminLTE - 主题概述和一般研究
  11. 创建自定义小部件
  12. 测试 - 单元 + 功能 + 验收 (opa) + 覆盖率
  13. Microsoft Access MDB
  14. 迁移批处理插入 csv

我的文章

文章被分成更多文件,因为每个文件在 wiki 上都有最大长度限制。

切换语言和 URL 中的语言

我已经写过关于翻译是如何工作的。在这里,我将展示如何切换语言并将其保存到 URL 中。因此,让我们将语言切换器添加到主菜单中。

echo Nav::widget([
 'options' => ['class' => 'navbar-nav navbar-right'],
 'items' => [
  ['label' => 'Language', 'items' => [
    ['label' => 'German' , 'url' => \yii\helpers\Url::current(['sys_lang' => 'de']) ],
    ['label' => 'English', 'url' => \yii\helpers\Url::current(['sys_lang' => 'en']) ],
   ],
  ]

现在,我们需要处理新的 GET 参数“sys_lang”并将其保存到 Session 中以保持新语言。最好的方法是创建一个 BaseController,所有控制器都将扩展它。它的内容如下所示

<?php
namespace app\controllers;
use yii\web\Controller;
class _BaseController extends Controller {
  public function beforeAction($action) {
    if (isset($_GET['sys_lang'])) {
      switch ($_GET['sys_lang']) {
        case 'de':
          $_SESSION['sys_lang'] = 'de-DE';
          break;
        case 'en':
          $_SESSION['sys_lang'] = 'en-US';
          break;
      }
    }
    if (!isset($_SESSION['sys_lang'])) {
      $_SESSION['sys_lang'] = \Yii::$app->sourceLanguage;
    }
    \Yii::$app->language = $_SESSION['sys_lang'];
    return true;
  }
}

如果您想在 URL 中包含 sys_lang,紧跟在域名之后,可以在 config/web.php 中创建以下 URL 规则

'components' => [
 // ...
 'urlManager' => [
  'enablePrettyUrl' => true,
  'showScriptName' => false,
  'rules' => [
   // https://yiiframework.cn/doc/api/2.0/yii-web-urlmanager#$rules-detail
   // https://stackoverflow.com/questions/2574181/yii-urlmanager-language-in-url
   // https://yiiframework.cn/wiki/294/seo-conform-multilingual-urls-language-selector-widget-i18n
   '<sys_lang:[a-z]{2}>' => 'site',
   '<sys_lang:[a-z]{2}>/<controller:\w+>' => '<controller>',
   '<sys_lang:[a-z]{2}>/<controller:\w+>/<action:\w+>' => '<controller>/<action>',
  ],
 ],
],

现在,语言切换链接将生成类似这样的 URL:http://myweb.com/en/site/index。如果没有规则,链接将是这样的:http://myweb.com/site/index?sys_lang=en。因此,此规则在两个方向上都有效。当 URL 被解析并且控制器被调用时,以及当使用 URL 助手创建新的 URL 时也是如此。

搜索和替换

我使用 Notepad++ 来使用 Regex 进行大规模更改。如果您按下 Ctrl+Shift+F,您将能够在所有文件中进行替换。

Yii::t()

Yii::t('text'  ,  'text'   ) // NO
Yii::t('text','text') // YES

search: Yii::t\('([^']*)'[^']*'([^']*)'[^\)]*\)
replace with: Yii::t\('$1','$2'\)

URL(在 Notepad++ 中)

return $this->redirect('/controller/action')->send(); // NO
return $this->redirect(['controller/action'])->send(); // YES

search: ->redirect\(['][/]([^']*)[']\)
replace: ->redirect\(['$1']\)

====

return $this->redirect('controller/action')->send(); // NO
return $this->redirect(['controller/action'])->send(); // YES

search: ->redirect\((['][^']*['])\)
replace: ->redirect\([$1]\)

PHP 短标签

search: (<\?)([^p=]) // <?if ...
replace: $1php $2 // <?php if ...
// note that sometimes <?xml can be found and it is valid, keep it

视图用法

search: render(Ajax|Partial)?\s*\(\s*['"]\s*[a-z0-9_\/]*(viewName)

虚拟化 - Vagrant 和 Docker - 为什么以及如何

Vagrant 和 Docker 都使用您指定的几乎任何 OS 或 SW 配置来创建虚拟机,而源代码位于您的本地磁盘上,因此您可以轻松地在您的 IDE 中,在您的操作系统下修改它们。

不仅可以用于 PHP 开发,还可以用于任何其他情况。

这有什么用?...您的生产服务器运行特定环境,您希望在同一系统上进行开发/测试。此外,您不必在本地安装 XAMPP、LAMP 或其他服务器。您只需启动虚拟机即可。此外,您可以与其他同事共享虚拟系统的配置,以便你们都使用相同环境。您还可以本地运行许多不同的 OS 系统,使用不同的 PHP 版本等。

Vagrant 和 Docker 的工作原理与 composer 或 NPM 相似。它是一个可用的 OS 镜像和其他 SW 的库,您只需选择一些组合即可。整个配置定义在一个文本文件中,名为 Vagrantfile 或 docker-compose.yml,您只需要几个命令即可运行它。调试也不成问题。

在 Vagrant 中运行 Yii 项目。(简化版本)

信息:本章使用 ScotchBox 中的 PHP 7.0。如果您需要 PHP 7.4,请阅读下一章,其中使用 CognacBox(将在测试后添加)。

基本概述和 Vagrant 配置

所有可用的 Vagrant OS 镜像列表在这里

这两个 Yii 演示应用程序已经包含 Vagrantfile,但它的设置对我来说不清楚 - 它太 PRO 了。所以我想发布我的简化版本,它使用名为scotch/box的 OS 镜像,您也可以将其用于非 Yii PHP 项目。(它有一些优点,缺点是免费版本中使用的 PHP 版本较旧)

Vagrantfile 存储在您的演示项目的根文件夹中。我的 Vagrantfile 只包含以下命令。

Vagrant.configure("2") do |config|
    config.vm.box = "scotch/box"
    config.vm.network "private_network", ip: "11.22.33.44"
    config.vm.hostname = "scotchbox"
    config.vm.synced_folder ".", "/var/www/public", :mount_options => ["dmode=777", "fmode=777"]
    config.vm.provision "shell", path: "./vagrant/vagrant.sh", privileged: false
end

# Virtual machine will be available on IP A.B.C.D (in our case 11.22.33.44, see above)
# Virtual can access your host machine on IP A.B.C.1 (this rule is given by Vagrant)

它需要文件 vagrant/vagrant.sh,因为我想稍微增强一下服务器。它包含以下内容


# Composer:
# (In case of composer errors, it can help to delete the vendor-folder and composer.lock file)
cd /var/www/public/
composer install

# You can automatically import your SQL (root/root, dbname scotchbox)
#mysql -u root -proot scotchbox < /var/www/public/vagrant/db.sql

# You can run migrations:
#php /var/www/public/protected/yiic.php migrate --interactive=0

# You can create folder and set 777 rights:
#mkdir /var/www/public/assets
#sudo chmod -R 777 /var/www/public/assets

# You can copy a file:
#cp /var/www/public/from.php /var/www/public/to.php

# Installing Xdebug v2 (Xdebug v3 has renamed config params!):
sudo apt-get update
sudo apt-get install php-xdebug

# Configuring Xdebug in php.ini:
# If things do not work, disable your firewall and restart IDE. It might help.
echo "" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "[XDebug]" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_enable=1" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_port=9000" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_autostart=1" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_log=/var/www/public/xdebug.log" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_connect_back=1" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.idekey=netbeans-xdebug" | sudo tee -a /etc/php/7.0/apache2/php.ini

# Important: Make sure that your IDE has identical settings: idekey and remote_port.
# NetBeans: Make sure your project is correctly setup. Right-click the project and select Properties / Run Cofigurations. "Project URL" and "Index file" must have correct values.

# Note:
# Use this if remote_connect_back does not work. 
# IP must correspond to the Vagrantfile, only the last number must be 1
#echo "xdebug.remote_handler=dbgp" | sudo tee -a /etc/php/7.0/apache2/php.ini
#echo "xdebug.remote_host=11.22.33.1" | sudo tee -a /etc/php/7.0/apache2/php.ini 

sudo service apache2 restart

... 所以在您的项目中创建这两个文件 ...

如果您想手动打开 php.ini 并粘贴此文本,您可以从此处复制它

// sudo nano /etc/php/7.0/apache2/php.ini
// (Xdebug v3 has renamed config params!)

[XDebug]
xdebug.remote_enable=1
xdebug.remote_port=9000
xdebug.remote_autostart=1
xdebug.remote_log=/var/www/public/xdebug.log
xdebug.remote_connect_back=1
xdebug.idekey=netbeans-xdebug

// Important: Make sure that your IDE has identical settings: idekey and remote_port.
// NetBeans: Make sure your project is correctly setup. Right-click the project and select Properties / Run Cofigurations. "Project URL" and "Index file" must have correct values.

要在 PhpStorm 中进行调试,请查看此视频

要通过 PhpStorm 连接到 MySQL,请查看MilanG 的此评论

安装和使用 Vagrant

首先,请安装 Vagrant 和 VirtualBox。

注意:不幸的是,如今 VirtualBox 在带有 M1 芯片的 ARM 架构 Mac 上无法使用。在这种情况下,请使用 Docker。

重要提示:如果命令“vagrant ssh”需要密码,请输入“vagrant”。

现在,只需打开您的命令行,导航到您的项目,您就可以启动

  • “vagrant -v”应显示您的版本,如果一切正常。
  • “vagrant init”创建一个新项目(您现在不需要它)
  • “vagrant up”运行 Vagrantfile 并创建/启动虚拟机

虚拟机运行后,您还可以调用这些命令

  • “vagrant ssh”打开 Linux shell - 如果提示您,请使用密码“vagrant”。
  • “vagrant halt”停止虚拟机
  • "vagrant reload" 会重启虚拟机,但 **不会运行 config.vm.provision 或启动现有 VAGRANT 虚拟机** - 您无需在每次重启电脑时都调用 "vagrant up"
  • "vagrant reload --provision" 会重启虚拟机并运行 config.vm.provision

在 Linux shell 中,您可以调用任何您想要的命令。

  • 要查找已安装的 Linux 版本: "cat /etc/os-release" 或 "lsb_release -a" 或 "hostnamectl"
  • 要获取 PHP 版本,请调用: "php -version"
  • 如果您没有权限运行 "mysql -v",您可以运行 "mysql -u {username} -p" .. 如果您知道登录信息
  • 当前 IP:hostname -I

在 "scotch/box" 中,我不使用 PhpMyAdmin,而是使用 Adminer。它是一个简单的 PHP 脚本,无需任何安装即可运行。只需将 adminer.php 脚本复制到您的 docroot 并通过浏览器访问它。使用与 Yii 配置中相同的登录信息。服务器将是 localhost。

在 Docker 中运行 Yii 项目(更新:下面添加了 xDebug!)

注意:我正在展示高级应用程序。我认为基本应用程序不会有太大区别。很棒的 Docker 教程在这里 here

Yii 项目已经为 Docker 做好了准备。要开始,您只需从 www.docker.com 安装 Docker,然后就可以继续使用本手册。

  • 下载应用程序模板并将其解压缩到任何文件夹
  • 打开命令行并导航到项目文件夹
  • 运行命令 **docker-compose up -d**
    • 参数 -d 将在后台作为服务运行 docker
    • 优点是命令行不会被阻塞 - 您将能够调用更多命令
  • 运行命令 **init** 初始化应用程序
  • 您也可以使用以下命令之一调用 **composer install**
    • docker-compose run --rm frontend composer install
    • docker-compose run --rm backend composer install

注意:**init** 和 **composer** 可以本地调用,不一定要通过 Docker 调用。它们只是将文件添加到您的文件夹中。

现在您将能够打开 URL

打开 common/config/main-local.php 并设置以下 DB 连接

  • host=**mysql** !!
  • dbname=yii2advanced
  • username=yii2advanced
  • password=secret
  • 值取自 docker-compose.yml

使用以下命令之一运行迁移

  • docker-compose run --rm frontend php yii migrate
  • docker-compose run --rm backend php yii migrate

现在转到前端,点击右上角的 "注册"

第二种方法是直接修改数据库中的表

  • 下载 adminer - 它是一个单文件数据库客户端:www.adminer.org/en
  • 将 Adminer 复制到 frontend\web\adminer.php
  • 使用以下地址打开 Adminer:https://:20080/adminer.php
  • 如果您的数据库没有密码,adminer 将拒绝工作。您需要 "破解" 它。
  • 使用以下登录信息并转到数据库 yii2advanced
  • server=**mysql** !!
  • username=yii2advanced
  • password=secret
  • 值取自 docker-compose.yml
  • 将您的第一个用户的 status 设置为 10

现在您拥有了您的帐户,您可以登录到后端

在 Docker 中启用 xDebug,yii 演示应用程序

只需在 docker-compose.yml 中添加部分 **environment** 即可,如下所示

services:

  frontend:
    build: frontend
    ports:
      - 20080:80
    volumes:
      # Re-use local composer cache via host-volume
      - ~/.composer-docker/cache:/root/.composer/cache:delegated
      # Mount source-code for development
      - ./:/app
    environment:
      PHP_ENABLE_XDEBUG: 1
      XDEBUG_CONFIG: "client_port=9000 start_with_request=yes idekey=netbeans-xdebug log_level=1 log=/app/xdebug.log discover_client_host=1"
      XDEBUG_MODE: "develop,debug"

这将允许您看到格式良好的 var_dump 值并在您的 IDE 中调试您的应用程序。

注意:您可以/必须根据您的 IDE 设置指定 **idekey** 和 **client_port**。此外,您的 Yii 项目也必须在 IDE 中配置良好。在 NetBeans 中,请确保 "项目 URL" 和 "索引文件" 在 "属性/运行配置" 中正确(右键单击项目)

注意 2:请记住,xDebug2 和 xDebug3 有不同的设置。详情here.

我在这上面花了大约 8 个小时。希望有人会喜欢它 :-) 可悲的是,此配置不在 docker-compose.yml 中。这将非常方便。

Docker - 自定义 php.ini

在部分 "volumes" 中添加此行

- ./myphp.ini:/usr/local/etc/php/conf.d/custom.ini

并创建文件 myphp.ini,该文件位于您的 Yii 应用程序的根目录中。例如,您可以输入 **html_errors=on** 和 **html_errors=off** 来测试文件是否已加载。重启 docker 并使用 **phpinfo()** 方法检查 PHP 文件中的结果。

如何进入 Docker 的 bash(cli,命令行)

在命令行中导航到您的 docker-project 文件夹并运行命令

  • docker ps
  • 这将列出您在 docker-compose.yml 中定义的所有服务

列表的最后一列是 NAMES。选择一个并复制其名称。然后运行命令

  • docker exec -it {NAME} /bin/bash
  • ... 其中 {NAME} 是您的服务名称。例如
  • docker exec -it yii-advanced_backend_1 /bin/bash

要找出使用了哪个 Linux,您可以调用 **cat /etc/os-release**。(或检查 Vagrant 章节以了解其他命令)

如果您想找到 php.ini,请键入 **php --ini**。找到它后,您可以将其复制到您的 yii-folder 中,如下所示

cp path/to/php.ini /app/myphp.ini

AdminLTE - 主题概述和一般研究

AdminLTE 是可用的管理主题之一。它目前有两个版本

  • AdminLTE v2 = 基于 Bootstrap 3 = 非常适合 Yii v2 应用程序
  • AdminLTE v3 = 基于 Bootstrap 4(很容易从 Bootstrap3 升级到 Bootstrap4 *)

* 从 Bootstrap3 升级到 Bootstrap4:https://www.youtube.com/watch?v=W1xxvngjep8

AdminLTE <= 2.3v2.4v3.0 的文档。请注意,某些 AdminLTE 功能只是第三方依赖项。例如地图。

还有许多其他管理主题

还有更多 Yii2 扩展,用于将 AdminLTE 集成到 Yii 项目中

我选择了 AdminLTE v2(因为它使用与 Yii2 演示相同的 Bootstrap),并且我测试了一些应该有助于实现的扩展。

但让我们从关于如何在 Yii2 演示应用程序中没有扩展的情况下使用 AdminLTE v2 的快速信息开始。

手动集成 v2.4 - 资产文件创建

  • 打开 documentation 并运行 composer 或下载所有依赖项的 ZIP 文件。
  • 打开 preview page 并将整个 HTML 代码复制到您的文本编辑器中。
  • 删除 BODY 部分中您不需要的那些部分(至少是以下内容:section class="content" 的内容)
  • 还要删除所有 SCRIPT 和 LINK 标签。我们将在稍后使用 AssetBundle 添加它们。

  • 打开现有的文件 views/layouts/main.php 并将重要的 PHP 调用复制到新文件。(Asset、beginPage、$content、Breadcrumbs 等等)
  • 现在您的布局已完成,您可以替换原始布局文件。

我们只需要创建资产文件来链接所有 SCRIPTs 和 LINKs

  • 将文件 assets/AppAsset 复制到 assets/LteAsset 并重命名其中的类。
  • 将所有 LINK- 和 SCRIPT- URL 复制到 LteAsset 中。
  • 跳过 jQuery 和 Bootstrap,它们是 Yii 的一部分。例如
namespace app\assets;
use yii\web\AssetBundle;
class LteAsset extends AssetBundle
{
    public $sourcePath = '@vendor/almasaeed2010/adminlte/';
    public $jsOptions = ['position' => \yii\web\View::POS_HEAD];  // POS_END cause conflict with YiiAsset  
    public $css = [
        'bower_components/font-awesome/css/font-awesome.min.css',
        'https://fonts.googleapis.ac.cn/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic',
        // etc
    ];
    public $js = [
        'bower_components/jquery-ui/jquery-ui.min.js',
        // etc
    ];
    public $depends = [
        'yii\web\YiiAsset',
        'yii\bootstrap\BootstrapAsset',
    ];
}
  • 刷新您的 Yii 页面并检查 "开发者工具" 中的网络错误。修复它们。

可能会出现此错误:"Headers already sent"

  • 这意味着您忘记将一些 PHP 代码从旧布局文件复制到新文件。

现在您已经完成了,您可以开始使用 AdminLTE 中的 HTML 和 JS 内容。因此,让我们检查一下将为我们完成此操作的扩展

Insolita 扩展

适用于许多 UI 项目:Boxes、Tile、Callout、Alerts 和 Chatbox。您只需要准备主布局文件和 Asset bundle,如上所述。它自 2018 年以来就没有更新过。

查看其 web 以了解我的评论。我展示了如何使用许多小部件。

源代码中的缺陷

vendor\insolita\yii2-adminlte-widgets\LteConst.php

  • 有一个拼写错误:COLOR_LIGHT_BLUE 应该是 'lightblue',而不是 'light-blue'

vendor\insolita\yii2-adminlte-widgets\CollapseBox.php

  • $collapseButtonTemplate 中的类应该是 "btn btn-box-tool",而不是 "btn {btnType} btn-xs"
  • (它影响可扩展框中的展开/折叠按钮)
  • $collapseButtonTemplate 必须修改才能启用从屏幕中移除框。即,必须在 prepareBoxTools() 方法中更改 data-widget 和 iconClass

LteBox

  • 框可能会隐藏在 "等待图标" 覆盖层后面。这是通过在框的 div 末尾添加以下 HTML 代码来实现的
    <div class="overlay"><i class="fa fa-refresh fa-spin"></i></div>
    
  • 这必须手动添加或通过修改 LteBox 添加

Yiister

它的网站解释了一切。非常有用:http://adminlte.yiister.ru 您只需要本文中的 Asset File,然后安装 Yiister 即可。可悲的是,它自 2015 年以来就没有更新过。提供用于渲染菜单、GridView、几个框、Fleshalerts 和 Callouts 的小部件。另外还有错误页面。

dmstr/yii2-adminlte-asset

在 AdminLTE 网站上正式提及。仅渲染菜单和警报。主要提供 Asset 文件和 Gii 模板。Gii 模板会自动修复 GridView 设计,但您可以在下面找到如何手动执行此操作。

其他增强功能

AdminLTE 使用字体 Source Sans Pro。如果您想要使用不同的字体,请在 Google Fonts 上选择它并修改布局文件,如下所示

<link href="https://fonts.googleapis.ac.cn/css2?family=Palanquin+Dark:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
 body {
    font-family: 'Palanquin Dark', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  } 
  
  h1,h2,h3,h4,h5,h6,
  .h1,.h2,.h3,.h4,.h5,.h6 {
    font-family: 'Palanquin Dark', sans-serif;
  }
</style>

要将 GridView 显示为应有的样子,请将其包装在以下 HTML 代码中

<div class="box box-primary">
  <div class="box-header">
    <h3 class="box-title"><i class="fa fa-table"></i>&nbsp;Grid caption</h3>
  </div>
  <div class="box-body"

  ... grid view ...

  </div>
</div>

您也可以更改 web/css/site.css 中的 glyphicon

a.asc:after {
    content: "\e155";
}

a.desc:after {
    content: "\e156";
}

基本上就是这样。现在我们知道如何使用 AdminLTE 并修复 GridView。至少需要一个扩展来渲染小部件,如上所述。

创建自定义小部件

查看关于 Widgets 的官方阅读内容,或者这个 explanation。我正在展示 this example,但我添加了 3 行。这两种类型的小部件都可以这样编码

namespace app\components;
use yii\base\Widget;
use yii\helpers\Html;

class HelloWidget extends Widget{
 public $message;
 public function init(){
  parent::init();
  if($this->message===null){
   $this->message= 'Welcome User';
  }else{
   $this->message= 'Welcome '.$this->message;
  }
  // ob_start();
  // ob_implicit_flush(false);
 }
 public function run(){
  // $content = ob_get_clean();
  return Html::encode($this->message); // . $content;
 }
}

// This widget is called like this:
echo HelloWidget::widget(['message' => ' Yii2.0']);

// After uncommenting my 4 comments you can use this
HelloWidget::begin(['message' => ' Yii2.0']);
echo 'My content';
HelloWidget::end();

测试 - 单元 + 功能 + 验收 (opa) + 覆盖率

运行测试很简单,因为两个演示应用程序都已准备就绪。使用命令行并导航到您的项目。然后键入

php ./vendor/bin/codecept run

这将运行单元测试和功能测试。它们定义在文件夹 tests/unit 和 tests/functional 中。功能测试在隐藏的浏览器中运行,我认为它们不适用于 JavaScript。为了测试复杂的 JavaScript,您需要验收测试。如何在文件 README.md 中找到它们或在 文档 中找到它们,这在两个演示应用程序中都有。如果您想在标准的 Chrome 或 Firefox 浏览器中运行这些测试,则需要 Java JDK 和文件 selenium-server*.jar。请参阅 README.md 中的链接。获得 JAR 文件后,将其放置到您的项目中并运行它

java -jar selenium-server-4.0.0.jar standalone

现在您可以重新运行您的测试。确保您在文件 acceptance.suite.yml 中的 WebDriver 部分具有项目的有效 URL。例如 https:///yii-basic/web。它取决于您的环境。还可以指定浏览器。对我来说,设置“browser: chrome”效果很好。如果您收到错误“WebDriver is not installed”,则需要调用此 composer 命令

composer require codeception/module-webdriver --dev

PS:还有一个文件 ChromeDriver,但我不确定它是否是“codeception/module-webdriver”的替代品或何时使用它。我还没有研究过。

如果您想查看代码覆盖率,请按照文档(上面的链接)中的说明进行操作。另外,请确保您的 PHP 包含 xDebug!并注意 xDebug2 和 xDebug3 的设置差异!如果缺少 xDebug,您将收到错误“No code coverage driver available”。

Microsoft Access MDB

在 Linux 下我还没有成功,但是当我将 Web 服务器安装在 Windows 上(例如 XAMPP Server)时,我能够安装“Microsoft Access Database Engine 2016 Redistributable”并使用 *.mdb 文件。

所以首先您应该安装带有 PHP 的 Web 服务器,并且您应该知道您是在安装 64 位还是 32 位版本。可能是 64 位。然后转到页面 Microsoft Access Database Engine 2016 Redistributable(或查找更新的版本,如果可用)并安装相应的包(32 位与 64 位)。

注意:如果您已经在相同位版本中安装了 MS Access,则可能不需要安装引擎。

然后您将能够在 DB 连接中使用以下 DSN 字符串。(代码属于文件 config/db.php)

<?php

$file = "C:\\xampp\\htdocs\\Database1.mdb";

return [
  'class' => 'yii\db\Connection',
	
  'dsn' => "odbc:DRIVER={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=$file;Uid=;Pwd=;",
  'username' => '',
  'password' => '',
  'charset' => 'utf8',
	
  //'schemaMap' => [
  //  'odbc'=> [
  //    'class'=>'yii\db\pgsql\Schema',
  //    'defaultSchema' => 'public' //specify your schema here
  //  ]
  //], 

  // Schema cache options (for production environment)
  //'enableSchemaCache' => true,
  //'schemaCacheDuration' => 60,
  //'schemaCache' => 'cache',
];

然后使用它来查询表

$data = Yii::$app->db->createCommand("SELECT * FROM TableX")->queryAll();
var_dump($data);

注意:如果您已经在与您的 PHP 不同的位版本中安装了 MS Access,则您将无法在正确的位版本中安装引擎。在这种情况下,您必须卸载 MS Access。

注意 2:如果您不知道 MDB 文件包含什么内容,Google Docs 向我推荐了 MDB、ACCDB 查看器和阅读器,它有效。

注意 3:Windows 10 中预装了名为

  • "ODBC Data Sources 32-bit"
  • "ODBC Data Sources 64-bit"
  • (只需按下 Win 键并键入“ODBC”)

打开您需要的那个,转到“系统 DSN”选项卡并单击“添加”。您将看到哪些驱动程序可用 - **只有这些驱动程序可以在 DSN 字符串中使用!**

如果只有“SQL Server”存在,那么您需要安装带有平台驱动程序的 Access Engine(或 MS Access)。您需要名为 cca "Microsoft Access Driver (*.mdb, *.accdb)" 的驱动程序

在我的情况下,引擎添加了以下 64 位驱动程序

  • Microsoft Access dBASE Driver (*.dbf, *.ndx, *.mdx)
  • Microsoft Access Driver (*.mdb, *.accdb)
  • Microsoft Access Text Driver (*.txt, *.csv)
  • Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)

那么 Linux 呢?

您也需要 MS Access 驱动程序,但 Microsoft 不提供它们。有一些第三方 MdbToolsEasySoft,但它们要么不完美,要么很贵。另外还有 Unix ODBC

对于 Java,有 Java JDBCJackcessUcanaccess

那么 Docker 呢?据我所知,您不能在 Linux 下运行 Windows 镜像,因此在这种情况下您将无法使用 Windows 的 ODBC 优势。您可以在 Windows 下使用 Linux 镜像,但我认为没有办法从虚拟 Linux 访问 ODBC 驱动程序。您必须尝试一下,我还没有测试过。

迁移批量插入 csv

如果您想将 CSV 导入 Yii2 迁移中的 DB,您可以创建此“迁移基类”并将其用作实际迁移的父类。然后您可以使用方法 batchInsertCsv()。

<?php

namespace app\components;

use yii\db\Migration;

class BaseMigration extends Migration
{
    /**
     * @param $filename Example: DIR_ROOT . DIRECTORY_SEPARATOR . "file.csv"
     * @param $table The target table name
     * @param $csvToSqlColMapping [csvColName => sqlColName] (if $containsHeaderRow = true) or [csvColIndex => sqlColName] (if $containsHeaderRow = false)
     * @param bool $containsHeaderRow If the header with CSV col names is present
     * @param int $batchSize How many rows will be inserted in each batch
     * @throws Exception
     */
    public function batchInsertCsv($filename, $table, $csvToSqlColMapping, $containsHeaderRow = false, $batchSize = 10000, $separator = ';')
    {
        if (!file_exists($filename)) {
            throw new \Exception("File " . $filename . " not found");
        }

        // If you see number 1 in first inserted row and column, most likely BOM causes this.
        // Some Textfiles begin with 239 187 191 (EF BB BF in hex)
        // bite order mark https://en.wikipedia.org/wiki/Byte_order_mark
        // Let's trim it on the first row.
        $bom = pack('H*', 'EFBBBF');

        $handle = fopen($filename, "r");
        $lineNumber = 1;
        $header = [];
        $rows = [];
        $sqlColNames = array_values($csvToSqlColMapping);
        $batch = 0;

        if ($containsHeaderRow) {
            if (($raw_string = fgets($handle)) !== false) {
                $header = str_getcsv(trim($raw_string, $bom), $separator);
            }
        }

        // Iterate over every line of the file
        while (($raw_string = fgets($handle)) !== false) {
            $dataArray = str_getcsv(trim($raw_string, $bom), $separator);

            if ($containsHeaderRow) {
                $dataArray = array_combine($header, $dataArray);
            }

            $tmp = [];
            foreach ($csvToSqlColMapping as $csvCol => $sqlCol) {
                $tmp[] = trim($dataArray[$csvCol]);
            }
            $rows[] = $tmp;

            $lineNumber++;
            $batch++;

            if ($batch >= $batchSize) {
                $this->batchInsert($table, $sqlColNames, $rows);
                $rows = [];
                $batch = 0;
            }
        }
        fclose($handle);

        $this->batchInsert($table, $sqlColNames, $rows);
    }
}
]]>
0
[wiki] 如何将 Yii2 应用程序的所有电子邮件重定向到一个收件箱 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/wiki/2566/how-to-redirect-all-emails-to-one-inbox-on-yii2-applicationshttps://yiiframework.cn/wiki/2566/how-to-redirect-all-emails-to-one-inbox-on-yii2-applications glpzzz glpzzz

\yii\mail\BaseMailer::useFileTransport 是一款很棒的工具。如果您激活它,通过此邮件发送的所有电子邮件将被保存(默认情况下)在 @runtime/mail 上,而不是被发送,允许开发人员检查结果。

但是,如果我们想在我们的收件箱中实际收到电子邮件会怎样。当所有电子邮件都应该发送到一个帐户时,没有问题:将其设置为参数并在 params-local.php 中修改它(假设使用高级应用程序模板)。

当应用程序应该将电子邮件发送到不同的帐户并使用 replyTo、cc 和 bcc 字段时,就会出现大问题。几乎不可能用以前的方法来解决它,而且不使用大量的 if(YII_DEBUG)

好吧,接下来有一个解决方案

'useFileTransport' => true,
'fileTransportCallback' => function (\yii\mail\MailerInterface $mailer, \yii\mail\MessageInterface $message) {
    $message->attachContent(json_encode([
            'to' => $message->getTo(),
            'cc' => $message->getCc(),
            'bcc' => $message->getBcc(),
            'replyTo' => $message->getReplyTo(),
        ]), ['fileName' => 'metadata.json', 'contentType' => 'application/json'])
        ->setTo('debug@mydomain.com') // account to receive all the emails
        ->setCc(null)
        ->setBcc(null)
        ->setReplyTo(null);

    $mailer->useFileTransport = false;
    $mailer->send($message);
    $mailer->useFileTransport = true;

    return $mailer->generateMessageFileName();
}

它如何工作?fileTransportCallback 是回调以指定应用于在 @runtime/mail 上创建已保存电子邮件的文件名。它“拦截”发送电子邮件的过程,因此我们可以将其用于我们的目的。

  1. 附加一个包含真实收件人信息的 json 文件,以便我们可以对其进行审查
  2. 将收件人(TO)设置为我们希望接收所有电子邮件的电子邮件地址。
  3. 将其他收件人字段设置为 null
  4. 停用 useFileTransport
  5. 发送电子邮件
  6. 激活 useFileTransport
  7. 返回默认文件名(操作的日期时间)

通过这种方式,我们可以将所有电子邮件都接收在指定的帐户上,并将它们存储在 @runtime/mail 上。

在 Yii2 应用程序上查看电子邮件非常简单的助手。

最初发布在:https://glpzzz.github.io/2020/10/02/yii2-redirect-all-emails.html

]]>
0
[wiki] Yii2 中多个文件上传的 API Tue, 05 Jul 2022 03:01:39 +0000 https://yiiframework.cn/wiki/2565/api-of-multiple-file-uploading-in-yii2https://yiiframework.cn/wiki/2565/api-of-multiple-file-uploading-in-yii2 fezzymalek fezzymalek

在出现很多错误并且不知道如何在 yii2 中执行多个图像 API 之后,我终于在今天得到了它

这是我在论坛上提出的问题,它对我有用 https://forum.yiiframework.com/t/multiple-file-uploading-api-in-yii2/130519

在模型中实现此代码以进行 **多个文件上传**

public function rules()
    {
        return [
            [['post_id', 'media'], 'required'],
            [['post_id'], 'integer'],
            [['media'], 'file', 'maxFiles' => 10],//here is my file field
            [['created_at'], 'string', 'max' => 25],
            [['post_id'], 'exist', 'skipOnError' => true, 'targetClass' => Post::className(), 'targetAttribute' => ['post_id' => 'id']],
        ];
    }
    

您也可以在模型中添加扩展或任何 skiponempty 方法。

这是我在其中执行多个文件上传代码的控制器操作。

public function actionMultiple(){
        $model = new Media;
        $model->post_id = '2';
        if (Yii::$app->request->ispost) {
            $model->media = UploadedFile::getInstances($model, 'media');
            if ($model->media) {
                foreach ($model->media as $value) {
                    $model = new Media;
                    $model->post_id = '2';
                    $BasePath = Yii::$app->basePath.'/../images/post_images';
                    $filename = time().'-'.$value->baseName.'.'.$value->extension;
                    $model->media = $filename;
                    if ($model->save()) {
                        $value->saveAs($BasePath.$filename);
                    }
                }
                return array('status' => true, 'message' => 'Image Saved'); 
            }
        }
        return array('status' => true, 'data' => $model);
    }

如果有任何查询或问题,我会回复。

]]>
0
[wiki] 如何将 Yii2 应用程序的错误日志发送给开发人员 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/wiki/2564/how-to-email-error-logs-to-developer-on-yii2-appshttps://yiiframework.cn/wiki/2564/how-to-email-error-logs-to-developer-on-yii2-apps glpzzz glpzzz

日志记录是应用程序非常重要的功能。它可以让您知道每时每刻发生了什么。默认情况下,Yii2 基本应用程序和高级应用程序仅配置了一个 \yii\log\FileTarget 目标。

要接收带有应用程序消息的电子邮件,请将日志组件设置为电子邮件(或 Telegram 或 Slack)传输,而不是(或除了)文件传输

'components' => [
    // ...
    'log' => [
         'targets' => [
             [
                 'class' => 'yii\log\EmailTarget',
                 'mailer' => 'mailer',
                 'levels' => ['error', 'warning'],
                 'message' => [
                     'from' => ['log@example.com'],
                     'to' => ['developer1@example.com', 'developer2@example.com'],
                     'subject' => 'Log message',
                 ],
             ],
         ],
    ],
    // ...
],

\yii\log\EmailTarget 组件是记录消息的另一种方式,在这种情况下,通过应用程序的 mailer 组件以 EmailTarget 配置的 mailer 属性中指定的电子邮件发送它们。请注意,您也可以指定消息属性以及应该通过此目标发送的哪些级别消息。

如果您想通过电子邮件以外的其他平台接收消息,则还有其他组件代表日志目标

或者您可以通过子类化 \yii\log\Target 来实现您自己的子类

]]>
0
[wiki] 如何将 Schema.org 标记添加到 Yii2 页面 Fri, 11 Sep 2020 22:09:55 +0000 https://yiiframework.cn/wiki/2560/how-to-add-schema-org-markup-to-yii2-pageshttps://yiiframework.cn/wiki/2560/how-to-add-schema-org-markup-to-yii2-pages glpzzz glpzzz

https://schema.org 是一种标记系统,允许在网页上嵌入结构化数据,以便搜索引擎和其他应用程序使用。让我们看看如何使用 **JSON-LD** 在基于 Yii2 的网站上的页面中添加 Schema.org。

基本上我们需要在我们的页面中嵌入类似的东西

<script type="application/ld+json">
{ 
  "@context": "http://schema.org/",
  "@type": "Movie",
  "name": "Avatar",
  "director": 
    { 
       "@type": "Person",
       "name": "James Cameron",
       "birthDate": "1954-08-16"
    },
  "genre": "Science fiction",
  "trailer": "../movies/avatar-theatrical-trailer.html" 
}
</script>

但我们不喜欢在 Yii2 上编写这样的脚本,所以让我们尝试以其他更 PHP 的方式来做。

在布局中,我们可以为我们的网站定义一些通用标记,因此我们在 @app/views/layouts/main.php 文件的开头添加以下代码段

<?= \yii\helpers\Html::script(isset($this->params['schema'])
    ? $this->params['schema']
    : \yii\helpers\Json::encode([
        '@context' => 'https://schema.org',
        '@type' => 'WebSite',
        'name' => Yii::$app->name,
        'image' => $this->image,
        'url' => Yi::$app->homeUrl,
        'descriptions' => $this->description,
        'author' => [
            '@type' => 'Organization',
            'name' => Yii::$app->name,
            'url' => 'https://www.hogarencuba.com',
            'telephone' => '+5352381595',
        ]
    ]), [
    'type' => 'application/ld+json',
]) ?>

这里我们使用 Html::script($content, $options) 来包含具有必要 type 选项的脚本,以及 Json::encode($value, $options) 来生成 JSON。我们还使用一个名为 schema 的页面参数,以允许从其他页面覆盖标记。例如,在 @app/views/real-estate/view.php 中,我们使用

$this->params['schema'] = \yii\helpers\Json::encode([
    '@context' => 'https://schema.org',
    '@type' => 'Product',
    'name' => $model->title,
    'description' => $model->description,
    'image' => array_map(function ($item) {
        return $item->url;
    }, $model->images),
    'category' => $model->type->description_es,
    'productID' => $model->code,
    'identifier' => $model->code,
    'sku' => $model->code,
    'url' => \yii\helpers\Url::current(),
    'brand' => [
        '@type' => 'Organization',
        'name' => Yii::$app->name,
        'url' => 'https://www.hogarencuba.com',
        'telephone' => '+5352381595',
    ],
    'offers' => [
        '@type' => 'Offer',
        'availability' => 'InStock',
        'url' => \yii\helpers\Url::current(),
        'priceCurrency' => 'CUC',
        'price' => $model->price,
        'priceValidUntil' => date('Y-m-d', strtotime(date("Y-m-d", time()) . " + 365 day")),
        'itemCondition' => 'https://schema.org/UsedCondition',
        'sku' => $model->code,
        'identifier' => $model->code,
        'image' => $model->images[0],
        'category' => $model->type->description_es,
        'offeredBy' => [
            '@type' => 'Organization',
            'name' => Yii::$app->name,
            'url' => 'https://www.hogarencuba.com',
            'telephone' => '+5352381595',
        ]
    ]
]);

这里我们用更复杂的标记重新定义了此页面的模式:一个带有报价的产品。

这样,我们网站上的所有页面都会定义一个 schema.org 标记:在布局中,我们有一个默认值,在其他页面中,我们可以通过设置 $this->params['schema'] 上的值来重新定义。

]]>
0
[wiki] 如何将 Open Graph 和 Twitter Card 标签添加到 Yii2 网站。 2024 年 9 月 19 日星期四 12:05:58 +0000 https://yiiframework.cn/wiki/2559/how-to-add-open-graph-and-twitter-card-tags-to-yii2-websitehttps://yiiframework.cn/wiki/2559/how-to-add-open-graph-and-twitter-card-tags-to-yii2-website glpzzz glpzzz

OpenGraphTwitter Cards 是两个元数据集,允许描述网页并使其更容易被 Facebook 和 Twitter 分别理解。

要添加到简单网页中的元标签很多,所以让我们使用 TaggedView

此组件覆盖了 yii\web\View,为其添加了更多属性,允许在每个视图中设置值。通常我们使用以下方法设置页面标题

$this->title = $model->title;

现在,使用 **TaggedView**,我们可以设置

$this->title = $model->title;
$this->description = $model->abstract;
$this->image = $model->image;
$this->keywords = ['foo', 'bar'];

这将为该页面生成正确的 OpenGraph、Twitter Card 和 HTML 元描述标签。

此外,我们可以在组件配置中为每个标签定义默认值,这些值将对每个页面可用,并且仅在像前面的示例一样重新定义时才会被覆盖。

'components' => [
    //...
    'view' => [
        'class' => 'daxslab\taggedview\View',
        'site_name' => '',
        'author' => '',
        'locale' => '',
        'generator' => '',
        'updated_time' => '',
    ],
    //...
]

某些属性有默认值,比如site_name默认值为Yii::$app->name

网站使用结果

<title>¿Deseas comprar o vender una casa en Cuba? | HogarEnCuba, para comprar y vender casas en Cuba</title>
<meta name="author" content="Daxslab (https://www.daxslab.com)">
<meta name="description" content="Hay 580 casas...">
<meta name="generator" content="Yii2 PHP Framework (https://yiiframework.cn)">
<meta name="keywords" content="HogarEnCuba, ...">
<meta name="robots" content="follow">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:description" content="Hay 580 casas...">
<meta name="twitter:image" content="https://www.hogarencuba.com/images/main-identifier_es.png">
<meta name="twitter:site" content="HogarEnCuba">
<meta name="twitter:title" content="¿Deseas comprar o vender una casa en Cuba?">
<meta name="twitter:type" content="website">
<meta name="twitter:url" content="https://www.hogarencuba.com/">
<meta property="og:description" content="Hay 580 casas...">
<meta property="og:image" content="https://www.hogarencuba.com/images/main-identifier_es.png">
<meta property="og:locale" content="es">
<meta property="og:site_name" content="HogarEnCuba">
<meta property="og:title" content="¿Deseas comprar o vender una casa en Cuba?">
<meta property="og:type" content="website">
<meta property="og:updated_time" content="10 sept. 2020 9:43:00">
]]>
0
[wiki] Yii v2 代码片段指南 II Thu, 11 Nov 2021 13:47:12 +0000 https://yiiframework.cn/wiki/2558/yii-v2-snippet-guide-iihttps://yiiframework.cn/wiki/2558/yii-v2-snippet-guide-ii rackycz rackycz
  1. 我的文章
  2. 连接到 MSSQL
  3. 在 Yii2 项目中使用 MSSQL 数据库作为第二个数据库
  4. 在 Gii 中为远程 MSSQL 表创建模型
  5. Yii 2 中的 PhpExcel/PhpSpreadsheet 以及将二进制内容发送到浏览器
  6. PDF - UTF + 1D & 2D 条形码 - TCPDF
  7. 自定义格式器 - asDecimalOrInteger
  8. 在带有父模型的 GridView 中显示子模型的 SUM
  9. 按关联列排序和搜索
  10. 将二进制数据作为文件发送到浏览器 - 解码 base64

我的文章

文章被分成更多文件,因为每个文件在 wiki 上都有最大长度限制。

连接到 MSSQL

您需要在 PHP 中使用 MSSQL 驱动程序。您可以通过编程方式列出它们或测试它们的存在,如下所示

var_dump(\PDO::getAvailableDrivers());

if (in_array('sqlsrv', \PDO::getAvailableDrivers())) {
  // ... MsSQL driver is available, do something
}

根据您的系统,您必须下载不同的驱动程序。差异是 x64 与 x86 以及 ThreadSafe 与 nonThreadSafe。在 Windows 中,我总是使用 ThreadSafe。 说明.

最新的 PHP 驱动程序 在这里.

  • 驱动程序 v5.8 = PHP 7.2 - 7.4

旧的 PHP 驱动程序 在这里.

  • 驱动程序 v4.0 = PHP 7.0 - 7.1
  • 驱动程序 v3.2 = PHP 5.x

下载并解压缩驱动程序后,选择一个 DLL 文件并将其放入“php/ext”文件夹中。在 Windows 中,它可能位于此处:“C:\xampp\php\ext”

注意:在某些情况下,您可能还需要 这些 OBDC 驱动程序,但我不知道何时需要

现在必须修改文件 php.ini。在 Windows 中,它可能位于此处:“C:\xampp\php\php.ini”。打开它并搜索以“extension”开头的行,并将以下内容粘贴到其中

extension={filename.dll}
// Example:
extension=php_pdo_sqlsrv_74_ts_x64.dll

现在重启 Apache 并访问 phpinfo() 网页。您应该看到“pdo_sqlsrv”部分。如果您使用的是 XAMPP,它可能位于此 URL 上: https:///dashboard/phpinfo.php.

然后,只需在 Yii2 配置中添加对 MSSQL 数据库的连接即可。在我的情况下,数据库是远程的,因此我需要创建第二个数据库连接。阅读下一章了解如何操作。

在 Yii2 项目中使用 MSSQL 数据库作为第二个数据库

在 yii-config 中,添加第二个数据库的方式如下所示

'db' => $db, // the original DB
'db2'=>[
  'class' => 'yii\db\Connection',
  'driverName' => 'sqlsrv',
  // I was not able to specify database like this: 
  // 'dsn' => 'sqlsrv:Server={serverName};Database={dbName}',
  'dsn' => 'sqlsrv:Server={serverName}', 
  'username' => '{username}',
  'password' => '{pwd}',
  'charset' => 'utf8',
],

就是这样。现在您可以像这样测试您的数据库

$result = Yii::$app->db2->createCommand('SELECT * FROM {tblname}')->queryAll();
var_dump($result);

请注意,在 MSSQL 中,您可以使用更长的表名。例如:CATEGORY.SCHEMA.TBL_NAME

您的第一个测试模型可以像这样(文件 MyMsModel.php)

namespace app\models;
use Yii;
use yii\helpers\ArrayHelper;
class MyMsModel extends \yii\db\ActiveRecord
{
  public static function getDb()
  {
    return \Yii::$app->db2; // or Yii::$app->get('db2');
  }
  public static function tableName()
  {
    return 'CATEGORY.SCHEMA.TBL_NAME'; // or SCHEMA.TBL_NAME
  }
}

使用

$result = MyMsModel::find()->limit(2)->all();
var_dump($result);

在 Gii 中为远程 MSSQL 表创建模型

添加第二个数据库后(如上所述),转到 Gii 中的模型生成器。将 DB 连接更改为您在 yii-config 中命名的连接(在上面的示例中为“db2”),并将表名设置为以下格式:SCHEMA.TBL_NAME。如果 MSSQL 服务器具有多个数据库,其中一个将被设置为主数据库。我认为这将被使用。我还没有成功更改数据库。数据库可以在 DSN 字符串中设置,但在我的情况下,它没有效果。

Yii 2 中的 PhpExcel/PhpSpreadsheet 以及将二进制内容发送到浏览器

在之前的章节中,我展示了如何在 Yii 1 中使用 PhpExcel。现在我也需要在 Yii 2 中使用它,而且非常简单。

注意:PhpExcel 已被弃用,并被 PhpSpreadsheet 替换。

// 1) Command line:
// This downloads everything to folder "vendor"
composer require phpoffice/phpspreadsheet --prefer-source
// --prefer-source ... also documentation and samples are downloaded 
// ... adds cca 40MB and 1400 files 
// ... only for devel system

// 2) PHP:
// Now you can directly use the package without any configuration:
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();

// Uncomment following rows if you want to set col width:
//$sheet->getColumnDimension('A')->setAutoSize(false);
//$sheet->getColumnDimension('A')->setWidth("50");

$sheet->setCellValue('A1', 'Hello World !');

$writer = new Xlsx($spreadsheet);

// You can save the file on the server:
// $writer->save('hello_world.xlsx'); 

// Or you can send the file directly to the browser so user can download it:
// header('Content-Type: application/vnd.ms-excel'); // This is probably for older XLS files.
header('Content-Type: application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); // This is for XLSX files (they are basically zip files).
header('Content-Disposition: attachment;filename="filename.xlsx"');
header('Cache-Control: max-age=0');
$writer->save('php://output');
exit();

感谢 DbCreator 的关于如何将 XLSX 发送到浏览器的想法。但是,不应调用 exit() 或 die()。请阅读链接。

以下是我的想法,它源于 Yii2 中的 renderPhpFile() 方法

ob_start();
ob_implicit_flush(false);
$writer->save('php://output');
$file = ob_get_clean();

return \Yii::$app->response->sendContentAsFile($file, 'file.xlsx',[
  'mimeType' => 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'inline' => false
]);

这对我也适用

$tmpFileName = uniqid('file_').'.xlsx';
$writer->save($tmpFileName);    
header('Content-Type: application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); 
header('Content-Disposition: attachment;filename="filename.xlsx"');
header('Cache-Control: max-age=0');
echo file_get_contents($tmpFileName);
unlink($tmpFileName);
exit();

注意:但是不应调用 exit() 或 die()。请阅读上面的“DbCreator”链接。

PDF - UTF + 1D & 2D 条形码 - TCPDF

有关其他 PDF 创建器,请参阅本指南的第一部分

TCPDF 创建于 2002 年(我认为),如今(2020 年)正在被重写为现代 PHP 应用程序。我将介绍两者,但让我们从旧版本开始。

旧版本的 TCPDF

从 GitHub 下载并保存到文件夹中

{projectPath}/_tcpdf

在 web/index.php 中添加以下内容

require_once('../_tcpdf/tcpdf.php');

现在您可以使用任何示例来测试 TCPDF。例如:https://tcpdf.org/examples/example_001/

注意:您必须使用反斜杠调用构造函数

$pdf = new \TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);

注意:使用多种方法打印文本 - 请参阅 tcpdf.php 文件以获取详细信息

  • writeHTMLCell()
  • Multicell()
  • writeHTML()
  • Write()
  • Cell()
  • Text()

注意:将文件存储为 UTF8 无 BOM 格式,以便 PDF 中的变音符号正确显示。

导入新的 TTF 字体,操作如下

// this command creates filed in folder _tcpdf\fonts. Use the filename as the fontname in other commands.
$fontname = \TCPDF_FONTS::addTTFfont("path to TTF file", 'TrueTypeUnicode', '', 96);

现在您可以在 PHP 中像这样使用它

$pdf->SetFont($fontname, '', 24, '', true);

或者在 HTML 中

<font size="9" face="fontName" style="color: rgb(128, 128, 128);">ABC</font>

渲染方式如下

$pdf->writeHTML($html);

注意:在将 pageNr 和 totalPageCount 打印到页脚时,writeHTML() 无法像 示例 3 中所示的那样正确解释 getAliasNumPage() 和 getAliasNbPages() 方法。我必须使用 Text() 渲染方法并将数字正确放置,如下所示

$this->writeHTML($footerHtmlTable);
$this->SetTextColor('128'); // I have gray pageNr
$this->Text(185, 279, 'Page ' . $this->getAliasNumPage() . '/' . $this->getAliasNbPages());
$this->SetTextColor('0'); // returning black color

新版本的 TCPDF

...待续...

自定义格式器 - asDecimalOrInteger

如果我生成一个 PDF 发票,它包含许多数字,将它们打印为整数(如果不需要小数)会很好看。例如,数字 24 看起来比 24.00 更好,并且节省了空间。因此我创建了这样的格式器。原始灵感和操作方法见此

我的格式器如下所示

<?php

namespace app\myHelpers;

class MyFormatter extends \yii\i18n\Formatter {

  public function asDecimalOrInteger($value) {
    $intStr = (string) (int) $value; // 24.56 => "24" or 24 => "24"
    if ($intStr === (string) $value) {
      // If input was integer, we are comparing strings "24" and "24"
      return $this->asInteger($value);
    }
    if (( $intStr . '.00' === (string) $value)) {
      // If the input was decimal, but decimals were all zeros, it is an integer.
      return $this->asInteger($value);
    }
    // All other situations
    $decimal = $this->asDecimal($value);
    
    // Here I trim also the trailing zero.
    // Disadvantage is that String is returned, but in PDF it is not important
    return rtrim((string)$decimal, "0"); 
  }

}

用法很简单。阅读上面的链接并点赞 karpy47 或者参见下方

// file config/web.php
'components' => [
    'formatter' => [
        'class' => 'app\myHelpers\MyFormatter',
   ],
],

整个 Yii 中只有一个格式器,您可以扩展它 = 您可以添加更多方法,格式器的其余部分将保持不变,因此您可以使用文档中提到的所有其他方法。

在带有父模型的 GridView 中显示子模型的 SUM

...可以通过将 MySQL 视图添加到您的数据库中,为其创建模型并在“ParentSearch”模型中将其用作基类来轻松完成。

让我们在发票及其项目的列表中显示它。发票位于“invoice”表中(模型 Invoice),其项目位于“invoice_item”中(模型 InvoiceItem)。现在我们需要将它们连接起来,并按价格(金额)的 SUM 进行排序和过滤。为了避免在 PHP 中进行计算,如果我们准备一个 MySQL 视图,数据库可以为我们执行此操作

CREATE VIEW v_invoice AS
SELECT invoice.*, 
SUM(invoice_item.units * invoice_item.price_per_unit) as amount,
COUNT(invoice_item.id) as items
FROM invoice 
LEFT JOIN invoice_item 
ON (invoice.id = invoice_item.id_invoice)
GROUP BY invoice.id

注意:在这里,您可以阅读为什么最好不要在 LEFT JOIN 中使用 COUNT(*)

这将在技术上将原始表“invoice”克隆到“v_invoice”,并将添加 2 个计算列:“amount” + “items”。现在您可以轻松地将此视图用作表(仅供读取),并将其显示在 GridView 中。如果您已经为“invoice”表创建了 GridView,则更改非常小。创建此模型

<?php
namespace app\models;
class v_Invoice extends Invoice
{
    public static function primaryKey()
    {
        // here is specified which column(s) create the fictive primary key in the mysql-view
        return ['id']; 
    }
    public static function tableName()
    {
        return 'v_invoice';
    }
}

.. 并在 InvoiceSearch 模型中用 v_Invoice 替换 Invoice(我认为有 2 个地方),并为这些新列添加规则。示例

public function rules()
{
  return [
    // ...
    [['amount'], 'number'], // decimal
    [['items'], 'integer'],
  ];
}

在 search() 方法中添加条件,以确定是否要按金额或项目进行过滤

if (trim($this->amount)!=='') {
  $query->andFilterWhere([
    'amount' => $this->amount
  ]);
}

在 GridView 中,您现在可以使用“amount”和“items”列作为本机列。过滤和排序将起作用。

危险:阅读以下内容,了解如何按关联列进行搜索和排序。如果您想将 MySQL 与另一个表连接起来,这可能无法正常工作。

我认为这种方法是最简单的实现目标的方法。优点是,只有在调用 search() 方法时才会使用 MySQL 视图 - 这意味着它是在发票列表中使用的。Web 的其他部分不受影响,因为它们使用的是原始 Invoice 模型。但是,如果您需要来自 Invoice 模型的某些特殊方法,您也可以在 v_Invoice 中使用它。如果数据被保存或更改,您必须始终修改原始表“invoice”。

按关联列排序和搜索

假设您有一个发票表和一个公司表。它们之间存在关系,您想显示发票列表以及每一行中相应的公司名称。您想按此列进行过滤和排序。

您的 GridView

<?= GridView::widget([
// ...
  'columns' => [
    // ...
    [
      'attribute'=>'company_name',
      'value'=>'companyRelation.name',
    ],

您的 InvoiceSearch 模型

class InvoiceSearch extends Invoice
{
  public $company_name;
  
  // ...
  
  public function rules() {
    return [
      // ...
      [['company_name'], 'safe'],
    ];
  }             

  // ...
  
  public function search($params) {
    // ...

    // You must use joinWith() in order to have both tables in one JOIN - then you can call WHERE and ORDER BY on the 2nd table. 
    // Explanation here:
    // https://stackoverflow.com/questions/25600048/what-is-the-difference-between-with-and-joinwith-in-yii2-and-when-to-use-them
    
    $query = Invoice::find()->joinWith('companyRelation');

    // Appending new sortable column:
    $sort = $dataProvider->getSort(); 
    $sort->attributes['company_name'] = [
      'asc' => ['table.column' => SORT_ASC],
      'desc' => ['table.column' => SORT_DESC],
      'label' => 'Some label',
      'default' => SORT_ASC            
    ];

    // ...
 
    if (trim($this->company_name)!=='') {
      $query->andFilterWhere(['like', 'table.column', $this->company_name]);
    }
  }

将二进制数据作为文件发送到浏览器 - 解码 base64

在我的 Yii v1 教程中,我介绍了以下方法:手动发送标题,然后调用 exit()。但是,调用 exit() 或 die() 不是一个好主意,因此我在 Yii v2 中发现了一种更好的方法。请参阅章节 安全(秘密)文件下载

动机:有时,您会收到使用 base64 编码为字符串的 PDF 文件。例如,来自 FedEx、DPD 或其他快递公司的带有条形码的标签,您的任务是将标签显示给用户。

对我来说,以下算法起作用

$pdfBase64 = 'JVBERi0xLjQ ... Y0CiUlRU9GCg==';

// First I create a fictive stream in a temporary file
// Read more about PHP wrappers: 
// https://php.ac.cn/manual/en/wrappers.php.php 
$stream = fopen('php://temp','r+');

// Decoded base64 is written into the stream
fwrite($stream, base64_decode($pdfBase64));

// And the stream is rewound back to the start so others can read it
rewind($stream);

// This row sets "Content-Type" header to none. Below I set it manually do application/pdf.
Yii::$app->response->format = Yii::$app->response::FORMAT_RAW;
Yii::$app->response->headers->set('Content-Type', 'application/pdf');
      
// This row will download the file. If you do not use the line, the file will be displayed in the browser.
// Details here:
// https://mdn.org.cn/en-US/docs/Web/HTTP/Headers#Downloads
// Yii::$app->response->headers->set('Content-Disposition','attachment; filename="hello.pdf"'); 
    
// Here is used the temporary stream
Yii::$app->response->stream = $stream;

// You can call following line, but you don't have to. Method send() is called automatically when current action ends:
// Details here:
// https://yiiframework.cn/doc/api/2.0/yii-web-response#sendContentAsFile()-detail
// return Yii::$app->response->send(); 

注意:如果需要,您可以添加更多标题。请查看我之前发表的文章(上面链接)。

]]>
0
[wiki] 通过 Pantahub 在 Raspberry Pi3 (RPI3) 中开始使用 Yii2 Tue, 22 Dec 2020 14:57:34 +0000 https://yiiframework.cn/wiki/2557/start-using-yii2-in-raspberry-pi3-rpi3-via-pantahubhttps://yiiframework.cn/wiki/2557/start-using-yii2-in-raspberry-pi3-rpi3-via-pantahub sirin_ibin sirin_ibin
  1. 按照 6 个步骤操作,使您的 RPI3 设备准备好部署 Yii2
  2. 按照 5 个步骤操作,将您的 Yii2 应用程序部署到设备

注意:Pantahub 是唯一一个可以共享和部署任何设备的 Linux 固件的地方。您可以在此处注册 @pantahub:http://www.pantahub.com

按照 6 个步骤操作,使您的 RPI3 设备准备好部署 Yii2

步骤 1:将 RPI3 初始稳定映像刻录到您的 SD 卡中。
a) 下载 RPI3 映像

点击下载: https://pantavisor-ci.s3.amazonaws.com/pv-initial-devices/tags/012-rc2/162943661/rpi3_initial_stable.img.xz

b) 解压设备镜像

运行 $ unxz rpi3_initial_stable.img.xz

c) 使用 Raspberry Pi Imager 1.2 将镜像写入 SD 卡

步骤 2:启动您的 RPI3
a) 插入您的 SD 卡并供电

步骤 3:在此注册 @pantahub http://www.pantahub.com
步骤 4:下载并安装 CLI 工具“pvr”

注意:pvr 是一款 CLI 工具,可用于通过 pantahub 平台与您的设备进行交互。

注意:使用 pvr,您可以像使用 git 树一样轻松地共享您的固件和项目。

注意:下载后,将 pvr 二进制文件移至您的 bin 文件夹。

Linux(AMD64):下载

Linux(ARM32v6):下载

Darwin(AMD64):下载

pvr clone; pvr commit; pvr post

从 github 源代码安装:$ go get gitlab.com/pantacor/pvr $ go build -o ~/bin/pvr gitlab.com/pantacor/pvr

注意:您需要在系统中安装“GOLANG”才能从 github 源代码构建 pvr。

步骤 5:检测并认领您的设备
a) 在您的 RPI3 和计算机/路由器之间连接一根 LAN 线缆。

b) 打开您的终端并运行 $ pvr scan

c) 认领您的设备

$ pvr claim -c merely-regular-gorilla https://api.pantahub.com:443/devices/5f1b9c44e193a Watch now on Amazon Prime Video 5000afa9901

d) 登录 Panthub.com 并检查新认领的设备是否出现在仪表板中。

步骤 6:使用设备的克隆 URL 将设备克隆到您的计算机

$ pvr clone https://pvr.pantahub.com/sirinibin/presently_learning_pelican/0 presently_learning_pelican

现在您的设备已准备好部署您的 Yii2 应用

按照 5 个步骤将您的 Yii2 应用部署到设备

步骤 1:移至设备根目录
 `$ cd presently_learning_pelican`
步骤 2:在设备中添加一个新的应用“yii2”

>sirinibin/yii2-basic-arm32v7:latest 是一个为具有 ARM32 架构的设备制作的 Docker Watch now on Amazon Prime Video 镜像 >> 您可以为您的自定义 Yii2 应用定制 Docker 镜像。

$ pvr app add yii2 --from=sirinibin/yii2-basic-arm32v7:latest

步骤 3:将更改部署到设备

$ pvr add . $ pvr commit $ pvr post

步骤 4:在 Pantahub.com 仪表板中检查设备状态更改,并等待状态变为 “已完成”

状态 1

状态 2

状态 3

状态 4

步骤 5:验证“yii2”应用部署

访问设备 IP:http://10.42.0.231/myapp1/web/ 在您的网页浏览器中。

您已完成!

]]>
0
[wiki] Yii2 - 升级到 Bootstrap 4 Fri, 20 Mar 2020 12:18:55 +0000 https://yiiframework.cn/wiki/2556/yii2-upgrading-to-bootstrap-4https://yiiframework.cn/wiki/2556/yii2-upgrading-to-bootstrap-4 RichardPillay RichardPillay

Yii2 - 从 Bootstrap3 转换为 Bootstrap4

撰写本文是因为尽管转换过程在很大程度上是轻松无痛的,但也存在一些小问题。这些问题并不难解决,但问题所在并不一目了然。

1 - 安装 Bootstrap4 我更喜欢直接使用 composer。更改项目根目录中的 composer.json

  • 找到包含 Bootstrap3 的行。
  • 复制该行,并更改新行

    • 将 bootstrap 更改为 bootstrap4
    • 现在转到 https://github.com/yiisoft/ - Github 上的 Yii2 存储库
    • 将版本字符串更改为该版本号,并将 ~ 更改为 ^
    • 之后,您应该得到如下所示的内容,版本号可能更高

      "yiisoft/yii2-bootstrap" : "~2.0.6", "yiisoft/yii2-bootstrap4" : "^2.0.8",

  • 保存文件,然后运行“composer update”
  • 使用您的 IDE、文本编辑器或您所拥有的任何其他工具找到所有使用 bootstrap 的地方,并将它们更改为 bootstrap4。我建议您搜索字符串 yii\bootstrap\ 并将其更改为 yii\bootstrap4\。但是,请注意 - 您的 IDE 可能要求转义反斜杠。例如,Eclipse 可以轻松地找到包含该字符串的所有文件,但搜索字符串必须使用双反斜杠,而替换字符串必须保留为单个反斜杠。
  • 现在运行所有测试并解决出现的问题。大多数失败将来自导航栏不再起作用这一事实,至少在我这里是这样。它仍然存在,只是渲染方式不同,在我的情况下它不可见。这是因为 Bootstrap4 更改了导航栏的某些元素。在我的情况下,有 14 个测试失败 - 其中许多测试失败是由于以某种方式使用导航栏内容造成的,例如查找登录链接以推断用户是否已登录。
    • 您需要了解问题才能解决这些问题,因此请查看 https://bootstrap.ac.cn/docs/4.0/migration/。特别是,查看有关导航栏更改的内容。最重要的是,Bootstrap4 不再指定背景,而 Bootstrap3 则指定了背景。
    • 在您的编辑器中打开 frontend/viewslayouts/main.php,然后在浏览器中查看您的站点。在我的情况下,除了品牌之外,没有导航栏,这是因为我更改了 Yii2 模板中提供的导航栏,它包含一个背景。由于其余部分是标准的,因此没有其他内容 - 没有色带条,也没有菜单项,尽管将鼠标悬停在该区域上会显示链接存在并且可以点击。
      • 找到以 NavBar::begin 开头的行,并查看其类选项。在我的情况下,它们是最初的:'class' => 'navbar-inverse navbar-fixed-top'
        • 正如我之前所说,没有包含背景,因此添加一个:bg-dark - 并再次检查。现在色带又回来了,但没有菜单项。
        • 右键单击色带并选择“检查元素”,或者在您的浏览器中执行任何操作以检查页面源代码。查看它将开始为您提供有关导航栏操作的线索。查看它,并参考 Bootstrap4 迁移指南,我有一种感觉,navbar-inverse 和 navbar-fixed-top 并没有起到任何作用。所以我删除了它们,刷新页面后,确认没有变化。
        • 在 Bootstrap 网站上阅读更多内容,我得到了之前提到的 bg-dark,以及用于文本的 navbar-light 或 navbar-dark 产生了结果。
        • 现在我没有菜单项,但我确实有一个按钮可以展开菜单。检查其属性告诉我它是“navbar-toggler”,Bootstrap 网站告诉我它是 Bootstrap4 的新功能,并且默认情况下它是折叠的,“navbar-expand”将默认情况下展开它。这很酷 - 我认为我将添加一个设置,让已登录的用户选择他们喜欢的设置。最后,我选择了 navbar-expand-md,它保持展开状态,除非屏幕宽度很窄。

最后,我将类行更改为如下所示,它为我提供了一个与原始导航栏非常相似的导航栏

        //Bootstrap3: 'class' => 'navbar-inverse navbar-fixed-top',
        //Changed for Bootstrap4: 
        'class' => 'navbar navbar-expand-md navbar-light bg-dark',


面包屑

注意 - 2020 年 3 月:有关面包屑的整个部分可能不再是问题。虽然我将该部分保留为教程,但在进行任何更改之前,请阅读 Davide 在用户评论中所说的话。

所以,这修复了我的导航栏。接下来,我注意到面包屑不太正确 - 分隔路径元素的斜杠不见了。为了准备大量调试,我去了 Bootstrap 网站寻找一些灵感。我不必再看了 - Bootstrap 4 要求每个面包屑元素都有一个“breadcrumb-item”类。在我花了一些时间查看 vendors/yiisoft/yii2/widgets/Breadcrumbs.php 以了解问题后,我发现只需要更改 itemTemplate 和 activeItemTemplate 即可。当然,由于这些是 Yii2 框架的一部分,您不想更改该文件,否则,它可能会在某个阶段进行更新,而您所做的所有更改都会丢失。由于这两个属性都是公开的,因此您可以从类外部更改它们,最简单的方法是在 frontend/views/main.php 中:`html

<div class="container">
    <?= Breadcrumbs::widget([
        'itemTemplate' => "\n\t<li class=\"breadcrumb-item\"><i>{link}</i></li>\n", // template for all links
        'activeItemTemplate' => "\t<li class=\"breadcrumb-item active\">{link}</li>\n", // template for the active link
        'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
    ]) ?>
    <?= Alert::widget() ?>
    <?= $content ?>
</div>```


数据网格操作列 我其中一个页面是 gii 为我生成的。在每一行上,它都有一组按钮,您可以点击它们来查看、编辑或删除该行。在 Bootstrap 4 下,操作列消失了。查看页面源代码显示它在那里,但我看不见它,也无法点击它。转到迁移指南,事实证明 Bootstrap 3 包含图标,而 Bootstrap 4 则不包含。我在 Yii2forum 中提出的问题 中得到了很多帮助。最后,我的解决方案是通过在 composer.json 的 require 部分中包含一行“fortawesome/font-awesome”: "^5.12.1" 来获得 FontAwesome 5 的本地副本,然后选择我想要的图标。我花了很多时间弄清楚如何做到这一点,但是当我完成时,它似乎很简单,几乎是反高潮。这是我在我的数据表单中所做的

            ['class' => 'yii\grid\ActionColumn',
                'buttons' => [
                    'update' =>  function($url,$model) {
                        return Html::a('<i class="fas fa-edit"></i>', $url, [
                            'title' => Yii::t('app', 'update')
                        ]);
                    },
                    'view' =>  function($url,$model) {
                        return Html::a('<i class="fas fa-eye"></i>', $url, [
                            'title' => Yii::t('app', 'view')
                        ]);
                    },
                    'delete' => function($url,$model) {
                        return Html::a('<i class="fas fa-trash"></i>', $url, [
                            'title' => Yii::t('app', 'delete')
                        ]);
                    }
                 ]
            ],


功能测试

没有看到任何更直观的东西,至少没有明显的东西,我现在运行了测试套件。这些测试以前都是通过的,但现在有几个测试失败了。其中一个是联系表格,因此我单独运行了该测试,测试告诉我它们失败了,因为它们看不见错误消息

1) ContactCest: Check contact submit no data
 Test  ../frontend/tests/functional/ContactCest.php:checkContactSubmitNoData
 
 Step  See "Name cannot be blank",".help-block"
 
 Fail  Element located either by name, CSS or XPath element with '.help-block' was not found.


另一方面,我可以在表单上看到错误消息,所以我使用了浏览器的页面源代码并发现 css 类不再是“help-block”,它已更改为“invalid-feedback”。很容易 - 在 frontend/tests/_support/FunctionalTester.php 中,我更改了预期的 css 类

public function seeValidationError($message)
{
    $this->see($message, '.invalid-feedback');
}

当然,这只是一个片段,只是举例说明。我发现必须在几个地方做同样的事情,但所有这些都易于找到和解决。


在这之后,运行我的测试没有发现其他问题,但我并不认为这意味着 没有 其他问题。虽然到目前为止一切似乎都正常,但我预计还有更多问题隐藏在幕后。不知何故,这些问题似乎不再那么难以克服了。

]]>
0