yii2tech/content

此包已被废弃且不再维护。未建议替代包。

Yii2内容管理系统

资助包维护!
klimov-paul
Patreon

安装次数: 2,895

依赖: 0

建议者: 0

安全: 0

星标: 55

关注者: 6

分支: 2

开放问题: 0

类型:yii2-extension

1.0.1 2018-07-02 14:16 UTC

This package is auto-updated.

Last update: 2022-01-10 10:35:40 UTC


README

12951949

Yii2内容管理系统


此扩展为Yii2提供内容管理系统。

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

Latest Stable Version Total Downloads Build Status

安装

通过composer安装此扩展是首选方式。

运行以下命令:

php composer.phar require --prefer-dist yii2tech/content

或添加以下内容到您的composer.json文件中的require部分:

"yii2tech/content": "*"

使用方法

此扩展为Yii2提供基本内容管理系统。通常,管理员需要更改网站内容,如静态页面('关于我们'、'如何使用'等)、电子邮件模板等。开发人员通常使用专用的数据库实体(表)来存储静态页面或电子邮件模板的内容。然而,仅使用数据库实体会创建一些问题,如定义默认(预填充)数据或更新现有应用程序。如果您需要为静态页面设置默认预置,以便部署的应用程序看起来不空或向现有网站添加额外的静态页面,并包含预填充内容,则您需要使用数据库迁移机制或其他方法来操作数据库记录。这并不实用 - 能够在VSC(Git、Mercurial等)程序代码中控制静态页面或电子邮件模板列表会更好。

此扩展通过'覆盖'原则解决内容管理任务:默认内容集由源代码文件定义,同时具有使用数据库存储覆盖默认内容的能力。

此扩展提供了一个特殊的Yii应用程序组件 - [[\yii2tech\content\Manager]],它提供了内容管理的高级接口。Manager通过2个内容存储进行操作

  • [[\yii2tech\content\Manager::$sourceStorage]] - 使用项目源文件作为默认内容源
  • [[\yii2tech\content\Manager::$overrideStorage]] - 使用DBMS存储来覆盖源内容

应用程序配置示例

return [
    'components' => [
        'pageContentManager' => [
            'class' => 'yii2tech\content\Manager',
            'sourceStorage' => [
                'class' => 'yii2tech\content\PhpStorage',
                'filePath' => '@app/data/pages',
            ],
            'overrideStorage' => [
                'class' => 'yii2tech\content\DbStorage',
                'table' => '{{%Page}}',
                'contentAttributes' => [
                    'title',
                    'body',
                ],
            ],
        ],
    ],
    // ...
];

在此示例中,默认静态页面内容位于项目文件中 '@app/data/content' 目录下。每个记录由一个单独的文件表示,如下所示

<?php
// file '@app/data/pages/about.php'
return [
    'title' => 'About',
    'body' => 'About page content',
];

覆盖内容将存储在数据库表'Page'中,可以使用以下DB迁移创建此表

class m??????_??????_createPage extends yii\db\Migration
{
    public function safeUp()
    {
        $tableName = 'Page';
        $columns = [
            'id' => $this->string(),
            'title' => $this->string(),
            'body' => $this->text(),
            'PRIMARY KEY([[id]])',
        ];
        $this->createTable($tableName, $columns);
    }

    public function safeDown()
    {
        $this->dropTable('Page');
    }
}

在渲染'关于'页面时,您应使用[[\yii2tech\content\Manager]]提供的抽象。方法[[\yii2tech\content\Manager::get()]]返回特定实体(例如特定页面)的内容集,包括所有相关内容部分:'标题'、'正文'等。例如

<?php
// file '@app/views/site/about.php'

use yii\bootstrap\Html;

/* @var $this yii\web\View */
/* @var $contentManager yii2tech\content\Manager */

$contentManager = Yii::$app->get('pageContentManager');
$contentItem = $contentManager->get('about');

$this->title = $contentItem->render('title');
?>
<div class="site-about">
    <?= $contentItem->render('body') ?>
</div>

如果'Page'表中有一个'id'等于'about'的记录,则将渲染此记录中的数据,否则将使用'@app/data/content/about.php'文件中的数据。

此扩展提供几个内容存储实现

  • [[yii2tech\content\PhpStorage]] - 使用PHP代码文件进行内容存储。
  • [[yii2tech\content\JsonStorage]] - 使用JSON格式的文件进行内容存储。
  • [[yii2tech\content\DbStorage]] - 使用关系数据库作为内容存储。
  • [[yii2tech\content\MongoDbStorage]] - 使用MongoDB作为内容存储。
  • [[yii2tech\content\ActiveRecordStorage]] - 使用ActiveRecord类进行内容存储。

请参阅特定存储类以获取更多详细信息。

注意:您可以组合使用任何'源'和'覆盖'存储的实现,但为了使'内容覆盖'方法中的基于项目文件的存储有意义(例如PhpStorageJsonStorage),应使用'源'。

模板渲染

仅存储最终HTML内容通常是不够的。在大多数情况下,内容管理通过内容模板进行操作,这些模板将在运行时填充特定数据。例如:您可能想在'关于'页面的内容中使用应用程序基本URL,以便引用图像和创建链接。因此,默认内容将如下所示

<?php
// file '@app/data/pages/about.php'
return [
    'title' => 'About',
    'body' => <<<HTML
<h1>About page content</h1>
<img src="{{appBaseUrl}}/images/about.jpg">
<a href="{{appBaseUrl}}">Home page</a>
...
HTML
];

在显示内容时,您可以像常规视图渲染一样将渲染参数传递给[[\yii2tech\content\Item::render()]]。

<?php
// file '@app/views/site/about.php'

use yii\bootstrap\Html;

/* @var $this yii\web\View */
/* @var $contentManager yii2tech\content\Manager */

$contentManager = Yii::$app->get('pageContentManager');
$contentItem = $contentManager->get('about');

$this->title = $contentItem->render('title');
?>
<div class="site-about">
    <?= $contentItem->render('body', [
        'appBaseUrl' => Yii::$app->request->baseUrl
    ]) ?>
</div>

您可以通过[[\yii2tech\content\Manager::$renderer]]设置内容渲染器。此扩展提供了一些内容渲染器的实现。

  • [[yii2tech\content\PlaceholderRenderer]] - 通过简单的字符串占位符替换执行内容渲染。
  • [[yii2tech\content\PhpEvalRenderer]] - 将内容作为PHP代码进行评估以执行内容渲染。
  • [[yii2tech\content\MustacheRenderer]] - 使用Mustache进行内容渲染。

请参阅特定渲染器类以获取更多详细信息。

注意:实际内容模板语法取决于实际使用的渲染器。

您可以使用[[\yii2tech\content\Manager::$defaultRenderData]]设置默认渲染数据,以便在每次渲染时使用,无需每次都传递它们。

return [
    'components' => [
        'pageContentManager' => [
            'class' => 'yii2tech\content\Manager',
            'defaultRenderData' => function () {
                return [
                    'appName' => Yii::$app->name,
                    'appBaseUrl' => Yii::$app->request->baseUrl,
                ];
            },
            // ...
        ],
    ],
    // ...
];

覆盖内容

您可以使用[[\yii2tech\content\Manager::save()]]保存内容覆盖。它将数据写入'覆盖存储',同时保持'源存储'中的内容完整。例如

/* @var $contentManager yii2tech\content\Manager */
$contentManager = Yii::$app->get('pageContentManager');

$contentManager->save('about', [
    'title' => 'Overridden Title',
    'body' => 'Overridden Body',
]);

echo $contentManager->get('about')->render('title'); // outputs 'Overridden Title'

注意:[[\yii2tech\content\Manager]]不会对源内容和覆盖内容之间的内容部分名称匹配进行检查。维护源和覆盖内容集之间的一致性是您的责任。然而,您可以使用[[\yii2tech\content\Item]]方法(见下文)来解决这个问题。

您可以使用[[\yii2tech\content\Manager::reset()]]方法恢复特定内容集的原始(默认)值。例如

/* @var $contentManager yii2tech\content\Manager */
$contentManager = Yii::$app->get('pageContentManager');

$contentManager->reset('about');

echo $contentManager->get('about')->render('title'); // outputs 'About'

您可以使用[[\yii2tech\content\Item]]提供的接口执行相同的数据操作。每个内容部分都可以通过与其相同的虚拟属性访问。例如

/* @var $contentManager yii2tech\content\Manager */
$contentManager = Yii::$app->get('pageContentManager');

$contentItem = $contentManager->get('about');
$contentItem->title = 'Overridden Title';
$contentItem->body = 'Overridden Body';
$contentItem->save(); // saves override

$contentItem->reset(); // restores origin (source)

[[\yii2tech\content\Item]]的使用解决了验证源和覆盖内容集之间匹配的问题。

保存额外内容

您可以将全新的内容集添加到'覆盖'存储中,即使它在'源'中没有匹配项。对此没有直接的限制。例如

/* @var $contentManager yii2tech\content\Manager */
$contentManager = Yii::$app->get('pageContentManager');

$contentManager->save('newPage', [
    'title' => 'New page',
    'body' => 'New page body',
]);

echo $contentManager->get('newPage')->render('title'); // outputs 'New page'

$contentManager->reset('newPage');

$contentManager->get('newPage'); // throws `\yii2tech\content\ItemNotFoundException` exception

这也可以使用[[\yii2tech\content\Item]]来完成。例如

use yii2tech\content\Item;

/* @var $contentManager yii2tech\content\Manager */
$contentManager = Yii::$app->get('pageContentManager');

$contentItem = new Item([
    'manager' => $contentManager,
]);
$contentItem->id = 'newPage';
$contentItem->setContents([
    'title' => 'New page',
    'body' => 'New page body',
]);
$contentItem->save();

创建内容管理Web界面

类 [[\yii2tech\content\Item]] 是 [[\yii\base\Model]] 的子类,它使用其内容部分作为模型属性。因此,[[\yii2tech\content\Item]] 的实例可用于创建网页表单、从请求数据中填充和保存。因此,执行内容覆盖的控制器可能如下所示

use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii2tech\content\ItemNotFoundException;
use Yii;

class PageController extends Controller
{
    /**
     * Updates particular content item, creating/updating an override.
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);

        if ($model->load(Yii::$app->request->post())) {
            if ($model->save()) {
                return $this->redirect(['index']);
            }
        }

        return $this->render('update', [
            'model' => $model,
        ]);
    }

    /**
     * Restores default values for particular content item, removing an override.
     */
    public function actionDefault($id)
    {
        $model = $this->findModel($id);
        $model->reset();
        return $this->redirect(['index']);
    }

    /**
     * @param string $id
     * @return \yii2tech\content\Item
     */
    protected function findModel($id)
    {
        /* @var $contentManager \yii2tech\content\Manager */
        $contentManager = Yii::$app->get('pageContentManager');
        try {
            $model = $contentManager->get($id);
        } catch (ItemNotFoundException $e) {
            throw new NotFoundHttpException('Requested page does not exist.');
        }
        return $model;
    }

    // ...
}

'update' 动作视图文件可能如下所示

<?php

use yii\widgets\ActiveForm;
use yii\helpers\Html;

/* @var $this yii\web\View */
/* @var $model yii2tech\content\Item */

$this->title = Yii::t('admin', 'Update Page: ') . $model->id;
?>
<div class="row">
    <?php $form = ActiveForm::begin(); ?>

    <?= $form->field($model, 'title')->textInput() ?>

    <?= $form->field($model, 'body')->textarea() ?>

    <div class="form-group">
        <?= Html::submitButton(Yii::t('admin', 'Save'), ['class' => 'btn btn-primary']) ?>
    </div>

    <?php ActiveForm::end(); ?>
</div>

您可以使用 [[\yii2tech\content\Manager::$itemConfig]] 为 [[\yii2tech\content\Item]] 模型设置自己的验证规则、属性标签和提示。例如

return [
    'components' => [
        'pageContentManager' => [
            'class' => 'yii2tech\content\Manager',
            'itemConfig' => [
                'class' => 'yii2tech\content\Item',
                'rules' => [
                    [['title', 'body'], 'required'],
                    ['title', 'string', 'max' => 255],
                ],
                'labels' => function () {
                    return [
                        'title' => Yii::t('page', 'Title'),
                        'body' => Yii::t('page', 'Body'),
                    ];
                },
                'hints' => function () {
                    return [
                        'title' => Yii::t('page', 'Page title...'),
                        'body' => Yii::t('page', 'Page body content...'),
                    ];
                },
            ],
            // ...
        ],
    ],
    // ...
];

注意:默认情况下,如果没有指定 [[\yii2tech\content\Item::$rules]],将为所有属性设置 'required' 验证器。

您还可以使用自己的类来创建内容项,以常规方式指定属性验证规则、标签和提示。

为了获取特定管理器上可用的内容项的完整列表,可以使用 [[\yii2tech\content\Manager::getAll()]] 方法。因此,列出所有可用页面的控制器操作可能如下所示

use yii\web\Controller;
use Yii;

class PageController extends Controller
{
    /**
     * Displays list of all available content items.
     */
    public function actionIndex()
    {
        /* @var $contentManager \yii2tech\content\Manager */
        $contentManager = Yii::$app->get('pageContentManager');

        $dataProvider = new ArrayDataProvider([
            'allModels' => $contentManager->getAll(),
            'pagination' => false,
        ]);

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

    // ...
}

然后,列表视图可能如下所示

<?php

use yii\grid\ActionColumn;
use yii\grid\GridView;

/* @var $this yii\web\View */
/* @var $dataProvider yii\data\ArrayDataProvider */
?>
<?= GridView::widget([
    'dataProvider' => $dataProvider,
    'columns' => [
        'id',
        'title',
        'body',
        [
            'class' => ActionColumn::className(),
            'template' => '{update} {default}',
            'buttons' => [
                'default' => function ($url) {
                    $icon = Html::tag('span', '', ['class' => 'glyphicon glyphicon-repeat']);
                    return Html::a($icon, $url, ['title' => 'Default', 'data-confirm' => 'Are you sure you want to restore defaults for this item?']);
                },
            ],
        ],
    ]
]); ?>

请注意!虽然使用 [[\yii2tech\content\Manager::getAll()]] 提供了快速简单的构建内容列表的方法,但它无论对计算资源还是内存消耗来说都不是高效的。在调用它时,如果您的相关内容项具有大量数据,程序可能会达到 PHP 内存限制。

处理元数据

处理内容模板时,您可能想设置有关它们的参考信息,例如为模板内使用的变量(占位符)提供描述。此类信息可能因特定内容集而异,例如 'about' 页面的变量可能与 'how-it-works' 页面的变量不同。因此,将此类元信息与默认内容一起保存是很好的做法,例如在源文件中。例如

<?php
// file '@app/data/pages/about.php'
return [
    'title' => 'About {{appName}}',
    'body' => 'About page content',
    'pageUrl' => ['/site/about'],
    'placeholderHints' => [
        '{{appName}}' => 'Application name'
    ],
];

您可以使用 [[\yii2tech\content\Manager::$metaDataContentParts]] 声明元内容部分列表,例如

return [
    'components' => [
        'pageContentManager' => [
            'class' => 'yii2tech\content\Manager',
            'metaDataContentParts' => [
                'pageUrl',
                'placeholderHints',
            ],
            // ...
        ],
    ],
    // ...
];

在 [[\yii2tech\content\Manager::$metaDataContentParts]] 中列出的内容部分将不会由 [[\yii2tech\content\Manager::get()]] 或 [[\yii2tech\content\Manager::getAll()]] 方法返回,因此不会填充到 [[\yii2tech\content\Item::$contents]] 中。您应该使用 [[\yii2tech\content\Manager::getMetaData()]] 或 [[\yii2tech\content\Item::getMetaData()]] 来检索它们。例如

/* @var $contentManager yii2tech\content\Manager */
$contentManager = Yii::$app->get('pageContentManager');

$contentItem = $contentManager->get('about');
var_dump($contentItem->has('pageUrl')); // outputs `false`

$metaData = $contentManager->getMetaData('about');
var_dump($metaData['pageUrl']);

$metaData = $contentItem->getMetaData();
var_dump($metaData['pageUrl']);

元数据的使用有助于构建用户友好的内容管理界面。

电子邮件模板管理

不要将示例限制在“页面”上。此扩展可用于多个目的,包括电子邮件模板管理。应用配置示例

return [
    'components' => [
        'mailContentManager' => [
            'class' => 'yii2tech\content\Manager',
            'sourceStorage' => [
                'class' => 'yii2tech\content\PhpStorage',
                'filePath' => '@app/data/mail',
            ],
            'overrideStorage' => [
                'class' => 'yii2tech\content\DbStorage',
                'table' => '{{%MailTemplate}}',
                'contentAttributes' => [
                    'subject',
                    'body',
                ],
            ],
        ],
    ],
    // ...
];

电子邮件消息编写示例

/* @var $contentManager yii2tech\content\Manager */
$contentManager = Yii::$app->get('mailContentManager');
$contentItem = $contentManager->get('contact');

Yii::$app->mailer->compose()
    ->setTo($admin->email)
    ->setFrom([Yii::$app->params['appEmail'] => Yii::$app->name])
    ->setSubject($contentItem->render('subject', ['appName' => Yii::$app->name, 'form' => $this]))
    ->setHtmlBody($contentItem->render('body', ['appName' => Yii::$app->name, 'form' => $this]))
    ->send();

您可以使用 [[\yii2tech\content\mail\MailerContentBehavior]] 行为来简化此任务,该行为在附加到邮件组件时,提供了一个快捷方法 composeFromContent(),用于快速从内容项中编写邮件消息。应用配置示例

return [
    'components' => [
        'mailContentManager' => [
            // ...
        ],
        'mailer' => [
            'class' => 'yii\swiftmailer\Mailer',
            'as content' => [
                'class' => 'yii2tech\content\mail\MailerContentBehavior',
                'contentManager' => 'mailContentManager',
                'messagePopulationMap' => [
                    'subject' => 'setSubject()',
                    'body' => 'setHtmlBody()',
                ],
            ],
        ],
    ],
    // ...
];

电子邮件消息编写示例

Yii::$app->mailer->composeFromContent('contact', ['appName' => Yii::$app->name, 'form' => $this])
    ->setTo($admin->email)
    ->setFrom([Yii::$app->params['appEmail'] => Yii::$app->name])
    ->send();

国际化

如果您有一个多语言项目,其内容应取决于所选界面语言,那么处理它的最佳方式是使用复合内容 ID。此类 ID 应包含实际语言作为其部分。例如:使用 'about' 而不是 'en/about'、'ru/about' 等。基于文件的存储,如 [[\yii2tech\content\PhpStorage]] 和 [[\yii2tech\content\JsonStorage]],能够处理子文件夹。因此,源文件的结构将如下所示

data/
    pages/
        en/
            about.php
            how-it-works.php
        ru/
            about.php
            how-it-works.php
    mail/
        en/
            contact.php
            reset-password.php
        ru/
            contact.php
            reset-password.php

检索特定内容项时,应使用 [[\yii\base\Application::$language]] 来组成其 ID。例如

/* @var $contentManager yii2tech\content\Manager */
$contentManager = Yii::$app->get('pageContentManager');
$contentItem = $contentManager->get(Yii::$app->language . '/about');