mirror of
https://github.com/silverstripe/silverstripe-blog
synced 2024-10-22 11:05:58 +02:00
NEW Moved URLSegmentExtension, BlogTag and BlogCateogry shared functionality into a trait
This commit is contained in:
parent
2ae1457068
commit
f5974d80a2
@ -8,11 +8,13 @@
|
|||||||
*
|
*
|
||||||
* @method Blog Blog()
|
* @method Blog Blog()
|
||||||
*
|
*
|
||||||
|
* @property string $Title
|
||||||
* @property string $URLSegment
|
* @property string $URLSegment
|
||||||
* @property int $BlogID
|
* @property int $BlogID
|
||||||
*/
|
*/
|
||||||
class BlogCategory extends DataObject implements CategorisationObject
|
class BlogCategory extends DataObject implements CategorisationObject
|
||||||
{
|
{
|
||||||
|
use BlogObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use an exception code so that attempted writes can continue on
|
* Use an exception code so that attempted writes can continue on
|
||||||
@ -21,13 +23,14 @@ class BlogCategory extends DataObject implements CategorisationObject
|
|||||||
* @const string
|
* @const string
|
||||||
* This must be a string because ValidationException has decided we can't use int
|
* This must be a string because ValidationException has decided we can't use int
|
||||||
*/
|
*/
|
||||||
const DUPLICATE_EXCEPTION = "DUPLICATE";
|
const DUPLICATE_EXCEPTION = 'DUPLICATE';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
'Title' => 'Varchar(255)',
|
'Title' => 'Varchar(255)',
|
||||||
|
'URLSegment' => 'Varchar(255)',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,140 +48,19 @@ class BlogCategory extends DataObject implements CategorisationObject
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
private static $extensions = array(
|
protected function getListUrlSegment()
|
||||||
'URLSegmentExtension',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return DataList
|
|
||||||
*/
|
|
||||||
public function BlogPosts()
|
|
||||||
{
|
{
|
||||||
$blogPosts = parent::BlogPosts();
|
return 'categories';
|
||||||
|
|
||||||
$this->extend("updateGetBlogPosts", $blogPosts);
|
|
||||||
|
|
||||||
return $blogPosts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getCMSFields()
|
protected function getDuplicateError()
|
||||||
{
|
{
|
||||||
$fields = new FieldList(
|
return _t('BlogCategory.Duplicate', 'A blog category already exists with that name.');
|
||||||
TextField::create('Title', _t('BlogCategory.Title', 'Title'))
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->extend('updateCMSFields', $fields);
|
|
||||||
|
|
||||||
return $fields;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function validate()
|
|
||||||
{
|
|
||||||
$validation = parent::validate();
|
|
||||||
if($validation->valid()) {
|
|
||||||
// Check for duplicate categories
|
|
||||||
$blog = $this->Blog();
|
|
||||||
if($blog && $blog->exists()) {
|
|
||||||
$existing = $blog->Categories()->filter('Title', $this->Title);
|
|
||||||
if($this->ID) {
|
|
||||||
$existing = $existing->exclude('ID', $this->ID);
|
|
||||||
}
|
|
||||||
if($existing->count() > 0) {
|
|
||||||
$validation->error(_t(
|
|
||||||
'BlogCategory.Duplicate',
|
|
||||||
'A blog category already exists with that name'
|
|
||||||
), BlogCategory::DUPLICATE_EXCEPTION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $validation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a relative link to this category.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getLink()
|
|
||||||
{
|
|
||||||
return Controller::join_links($this->Blog()->Link(), 'category', $this->URLSegment);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inherits from the parent blog or can be overwritten using a DataExtension.
|
|
||||||
*
|
|
||||||
* @param null|Member $member
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function canView($member = null)
|
|
||||||
{
|
|
||||||
$extended = $this->extendedCan(__FUNCTION__, $member);
|
|
||||||
|
|
||||||
if ($extended !== null) {
|
|
||||||
return $extended;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->Blog()->canView($member);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function canCreate($member = null, $context = array())
|
|
||||||
{
|
|
||||||
$extended = $this->extendedCan(__FUNCTION__, $member);
|
|
||||||
|
|
||||||
if ($extended !== null) {
|
|
||||||
return $extended;
|
|
||||||
}
|
|
||||||
|
|
||||||
$permission = Blog::config()->grant_user_permission;
|
|
||||||
|
|
||||||
return Permission::checkMember($member, $permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inherits from the parent blog or can be overwritten using a DataExtension.
|
|
||||||
*
|
|
||||||
* @param null|Member $member
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function canDelete($member = null)
|
|
||||||
{
|
|
||||||
$extended = $this->extendedCan(__FUNCTION__, $member);
|
|
||||||
|
|
||||||
if ($extended !== null) {
|
|
||||||
return $extended;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->Blog()->canEdit($member);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inherits from the parent blog or can be overwritten using a DataExtension.
|
|
||||||
*
|
|
||||||
* @param null|Member $member
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function canEdit($member = null)
|
|
||||||
{
|
|
||||||
$extended = $this->extendedCan(__FUNCTION__, $member);
|
|
||||||
|
|
||||||
if ($extended !== null) {
|
|
||||||
return $extended;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->Blog()->canEdit($member);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
class BlogTag extends DataObject implements CategorisationObject
|
class BlogTag extends DataObject implements CategorisationObject
|
||||||
{
|
{
|
||||||
|
use BlogObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use an exception code so that attempted writes can continue on
|
* Use an exception code so that attempted writes can continue on
|
||||||
@ -29,6 +30,7 @@ class BlogTag extends DataObject implements CategorisationObject
|
|||||||
*/
|
*/
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
'Title' => 'Varchar(255)',
|
'Title' => 'Varchar(255)',
|
||||||
|
'URLSegment' => 'Varchar(255)',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,140 +48,18 @@ class BlogTag extends DataObject implements CategorisationObject
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
private static $extensions = array(
|
protected function getListUrlSegment()
|
||||||
'URLSegmentExtension',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return DataList
|
|
||||||
*/
|
|
||||||
public function BlogPosts()
|
|
||||||
{
|
{
|
||||||
$blogPosts = parent::BlogPosts();
|
return 'tags';
|
||||||
|
|
||||||
$this->extend("updateGetBlogPosts", $blogPosts);
|
|
||||||
|
|
||||||
return $blogPosts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getCMSFields()
|
protected function getDuplicateError()
|
||||||
{
|
{
|
||||||
$fields = new FieldList(
|
return _t('BlogTag.Duplicate', 'A blog tags already exists with that name.');
|
||||||
TextField::create('Title', _t('BlogTag.Title', 'Title'))
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->extend('updateCMSFields', $fields);
|
|
||||||
|
|
||||||
return $fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function validate()
|
|
||||||
{
|
|
||||||
$validation = parent::validate();
|
|
||||||
if($validation->valid()) {
|
|
||||||
// Check for duplicate tags
|
|
||||||
$blog = $this->Blog();
|
|
||||||
if($blog && $blog->exists()) {
|
|
||||||
$existing = $blog->Tags()->filter('Title', $this->Title);
|
|
||||||
if($this->ID) {
|
|
||||||
$existing = $existing->exclude('ID', $this->ID);
|
|
||||||
}
|
|
||||||
if($existing->count() > 0) {
|
|
||||||
$validation->error(_t(
|
|
||||||
'BlogTag.Duplicate',
|
|
||||||
'A blog tags already exists with that name'
|
|
||||||
), BlogTag::DUPLICATE_EXCEPTION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $validation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a relative URL for the tag link.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getLink()
|
|
||||||
{
|
|
||||||
return Controller::join_links($this->Blog()->Link(), 'tag', $this->URLSegment);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inherits from the parent blog or can be overwritten using a DataExtension.
|
|
||||||
*
|
|
||||||
* @param null|Member $member
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function canView($member = null)
|
|
||||||
{
|
|
||||||
$extended = $this->extendedCan(__FUNCTION__, $member);
|
|
||||||
|
|
||||||
if ($extended !== null) {
|
|
||||||
return $extended;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->Blog()->canView($member);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function canCreate($member = null, $context = array())
|
|
||||||
{
|
|
||||||
$extended = $this->extendedCan(__FUNCTION__, $member);
|
|
||||||
|
|
||||||
if ($extended !== null) {
|
|
||||||
return $extended;
|
|
||||||
}
|
|
||||||
|
|
||||||
$permission = Blog::config()->grant_user_permission;
|
|
||||||
|
|
||||||
return Permission::checkMember($member, $permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inherits from the parent blog or can be overwritten using a DataExtension.
|
|
||||||
*
|
|
||||||
* @param null|Member $member
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function canDelete($member = null)
|
|
||||||
{
|
|
||||||
$extended = $this->extendedCan(__FUNCTION__, $member);
|
|
||||||
|
|
||||||
if ($extended !== null) {
|
|
||||||
return $extended;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->Blog()->canEdit($member);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inherits from the parent blog or can be overwritten using a DataExtension.
|
|
||||||
*
|
|
||||||
* @param null|Member $member
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function canEdit($member = null)
|
|
||||||
{
|
|
||||||
$extended = $this->extendedCan(__FUNCTION__, $member);
|
|
||||||
|
|
||||||
if ($extended !== null) {
|
|
||||||
return $extended;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->Blog()->canEdit($member);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
221
code/traits/BlogObject.php
Normal file
221
code/traits/BlogObject.php
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object shared by BlogTag and BlogCategory.
|
||||||
|
*
|
||||||
|
* @package silverstripe
|
||||||
|
* @subpackage blog
|
||||||
|
*/
|
||||||
|
trait BlogObject {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return DataList
|
||||||
|
*/
|
||||||
|
public function BlogPosts()
|
||||||
|
{
|
||||||
|
$blogPosts = parent::BlogPosts();
|
||||||
|
|
||||||
|
$this->extend('updateGetBlogPosts', $blogPosts);
|
||||||
|
|
||||||
|
return $blogPosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCMSFields()
|
||||||
|
{
|
||||||
|
$fields = new TabSet('Root',
|
||||||
|
new Tab('Main',
|
||||||
|
TextField::create('Title', _t(self::class . '.Title', 'Title'))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$fields = FieldList::create($fields);
|
||||||
|
$this->extend('updateCMSFields', $fields);
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function validate()
|
||||||
|
{
|
||||||
|
$validation = parent::validate();
|
||||||
|
if(!$validation->valid()) {
|
||||||
|
return $validation;
|
||||||
|
}
|
||||||
|
|
||||||
|
$blog = $this->Blog();
|
||||||
|
if(!$blog || !$blog->exists()) {
|
||||||
|
return $validation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->getDuplicatesByUrlSegment()->count() > 0) {
|
||||||
|
$validation->error($this->getDuplicateError(), self::DUPLICATE_EXCEPTION);
|
||||||
|
}
|
||||||
|
return $validation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a relative link to this category.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLink()
|
||||||
|
{
|
||||||
|
return Controller::join_links(
|
||||||
|
$this->Blog()->Link(),
|
||||||
|
$this->getListUrlSegment(),
|
||||||
|
$this->URLSegment
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherits from the parent blog or can be overwritten using a DataExtension.
|
||||||
|
*
|
||||||
|
* @param null|Member $member
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function canView($member = null)
|
||||||
|
{
|
||||||
|
$extended = $this->extendedCan(__FUNCTION__, $member);
|
||||||
|
|
||||||
|
if ($extended !== null) {
|
||||||
|
return $extended;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->Blog()->canView($member);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function canCreate($member = null, $context = array())
|
||||||
|
{
|
||||||
|
$extended = $this->extendedCan(__FUNCTION__, $member);
|
||||||
|
|
||||||
|
if ($extended !== null) {
|
||||||
|
return $extended;
|
||||||
|
}
|
||||||
|
|
||||||
|
$permission = Blog::config()->grant_user_permission;
|
||||||
|
|
||||||
|
return Permission::checkMember($member, $permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherits from the parent blog or can be overwritten using a DataExtension.
|
||||||
|
*
|
||||||
|
* @param null|Member $member
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function canDelete($member = null)
|
||||||
|
{
|
||||||
|
$extended = $this->extendedCan(__FUNCTION__, $member);
|
||||||
|
|
||||||
|
if ($extended !== null) {
|
||||||
|
return $extended;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->Blog()->canEdit($member);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherits from the parent blog or can be overwritten using a DataExtension.
|
||||||
|
*
|
||||||
|
* @param null|Member $member
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function canEdit($member = null)
|
||||||
|
{
|
||||||
|
$extended = $this->extendedCan(__FUNCTION__, $member);
|
||||||
|
|
||||||
|
if ($extended !== null) {
|
||||||
|
return $extended;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->Blog()->canEdit($member);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function onBeforeWrite()
|
||||||
|
{
|
||||||
|
parent::onBeforeWrite();
|
||||||
|
if(empty($this->URLSegment)) {
|
||||||
|
return $this->generateURLSegment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a unique URLSegment from the title.
|
||||||
|
*
|
||||||
|
* @param int $increment
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generateURLSegment($increment = 0)
|
||||||
|
{
|
||||||
|
$increment = (int) $increment;
|
||||||
|
$filter = new URLSegmentFilter();
|
||||||
|
|
||||||
|
$this->URLSegment = $filter->filter($this->owner->Title);
|
||||||
|
|
||||||
|
if ($increment > 0) {
|
||||||
|
$this->URLSegment .= '-' . $increment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->getDuplicatesByUrlSegment()->count() > 0) {
|
||||||
|
$this->owner->generateURLSegment($increment+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->owner->URLSegment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks for objects of the same type by url segment.
|
||||||
|
*
|
||||||
|
* @return DataList
|
||||||
|
*/
|
||||||
|
protected function getDuplicatesByUrlSegment()
|
||||||
|
{
|
||||||
|
$duplicates = DataList::create(self::class)->filter(array(
|
||||||
|
'URLSegment' => $this->URLSegment,
|
||||||
|
'BlogID' => (int) $this->BlogID,
|
||||||
|
));
|
||||||
|
|
||||||
|
if ($this->ID) {
|
||||||
|
$duplicates = $duplicates->exclude('ID', $this->ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $duplicates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This returns the url segment for the listing page.
|
||||||
|
* eg. 'categories' in /my-blog/categories/category-url
|
||||||
|
*
|
||||||
|
* This is not editable at the moment, but a method is being used incase we want
|
||||||
|
* to make it editable in the future. We can use this method to provide logic
|
||||||
|
* without replacing multiple areas of the code base. We're also not being opinionated
|
||||||
|
* about how the segment should be obtained at the moment and allowing for the
|
||||||
|
* implementation to decide.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
abstract protected function getListUrlSegment();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an error message for this object when it tries to write a duplicate.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
abstract protected function getDuplicateError();
|
||||||
|
|
||||||
|
}
|
@ -148,10 +148,12 @@ class BlogCategoryTest extends FunctionalTest
|
|||||||
$category = new BlogCategory();
|
$category = new BlogCategory();
|
||||||
$category->Title = 'Test';
|
$category->Title = 'Test';
|
||||||
$category->BlogID = $blog->ID;
|
$category->BlogID = $blog->ID;
|
||||||
|
$category->URLSegment = 'test';
|
||||||
$category->write();
|
$category->write();
|
||||||
|
|
||||||
$category = new BlogCategory();
|
$category = new BlogCategory();
|
||||||
$category->Title = 'Test';
|
$category->Title = 'Test';
|
||||||
|
$category->URLSegment = 'test';
|
||||||
$category->BlogID = $blog->ID;
|
$category->BlogID = $blog->ID;
|
||||||
try {
|
try {
|
||||||
$category->write();
|
$category->write();
|
||||||
|
@ -162,7 +162,7 @@ class BlogTagTest extends FunctionalTest
|
|||||||
$tag2->Title = 'cat test';
|
$tag2->Title = 'cat test';
|
||||||
$tag2->BlogID = $blog->ID;
|
$tag2->BlogID = $blog->ID;
|
||||||
$tag2->write();
|
$tag2->write();
|
||||||
$this->assertEquals('cat-test-0', $tag2->URLSegment);
|
$this->assertEquals('cat-test-1', $tag2->URLSegment);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,10 +174,12 @@ class BlogTagTest extends FunctionalTest
|
|||||||
$tag = new BlogTag();
|
$tag = new BlogTag();
|
||||||
$tag->Title = 'Test';
|
$tag->Title = 'Test';
|
||||||
$tag->BlogID = $blog->ID;
|
$tag->BlogID = $blog->ID;
|
||||||
|
$tag->URLSegment = 'test';
|
||||||
$tag->write();
|
$tag->write();
|
||||||
|
|
||||||
$tag = new BlogTag();
|
$tag = new BlogTag();
|
||||||
$tag->Title = 'Test';
|
$tag->Title = 'Test';
|
||||||
|
$tag->URLSegment = 'test';
|
||||||
$tag->BlogID = $blog->ID;
|
$tag->BlogID = $blog->ID;
|
||||||
try {
|
try {
|
||||||
$tag->write();
|
$tag->write();
|
||||||
|
Loading…
Reference in New Issue
Block a user