yii2tech / content
Yii2内容管理系统
Requires
- yiisoft/yii2: ~2.0.14
Suggests
- mustache/mustache: you need this package, if you wish to use `MustacheRenderer`
This package is auto-updated.
Last update: 2022-01-10 10:35:40 UTC
README
Yii2内容管理系统
此扩展为Yii2提供内容管理系统。
有关许可信息,请查看LICENSE文件。
安装
通过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类进行内容存储。
请参阅特定存储类以获取更多详细信息。
注意:您可以组合使用任何'源'和'覆盖'存储的实现,但为了使'内容覆盖'方法中的基于项目文件的存储有意义(例如
PhpStorage
或JsonStorage
),应使用'源'。
模板渲染
仅存储最终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');