protected/modules/store/components/behaviors/DCategoryTreeBehavior.php
<?php
namespace store\components\behaviors;
use Yii;
/**
* @author ElisDN <mail@elisdn.ru>
* @link http://www.elisdn.ru
* @version 1.4
*
* @property string $parentAttribute
* @property string $parentRelation
*
* @property integer[] $childsArray
* @property mixed $tabList
* @property string $path
* @property string $breadcrumbs
*/
class DCategoryTreeBehavior extends DCategoryBehavior
{
/**
* @var string model attribute
*/
public $parentAttribute = 'parent_id';
/**
* @var string model parent BELONGS_TO relation
*/
public $parentRelation = 'parent';
/**
* @var null Stat relation name (defined in defaultCriteria)
*/
public $statAttribute = null;
/**
* Returns array of primary keys of children items
* @param mixed $parent number, object or array of numbers
* @return array
*/
public function getChildsArray($parent = 0)
{
$parents = $this->processParents($parent);
$this->cached();
$criteria = $this->getOwnerCriteria();
$criteria->select = 't.'.$this->primaryKeyAttribute.', t.'.$this->titleAttribute.', t.'.$this->parentAttribute;
$command = $this->createFindCommand($criteria);
$items = $command->queryAll();
$this->clearOwnerCriteria();
$result = [];
foreach ($parents as $parent_id) {
$this->_childsArrayRecursive($items, $result, $parent_id);
}
return array_unique($result);
}
/**
* @param $items
* @param $result
* @param $parent_id
*/
protected function _childsArrayRecursive(&$items, &$result, $parent_id)
{
foreach ($items as $item) {
if ((int)$item[$this->parentAttribute] == (int)$parent_id) {
$result[] = $item[$this->primaryKeyAttribute];
$this->_childsArrayRecursive($items, $result, $item[$this->primaryKeyAttribute]);
}
}
}
/**
* Returns associated array ($id=>$fullTitle, $id=>$fullTitle, ...)
* @param mixed $parent number, object or array of numbers
* @return array
*/
public function getAssocList($parent = 0)
{
$this->cached();
$items = $this->getFullAssocData(
[
$this->primaryKeyAttribute,
$this->titleAttribute,
$this->parentAttribute,
],
$parent
);
$associated = [];
foreach ($items as $item) {
$associated[$item[$this->primaryKeyAttribute]] = $item;
}
$items = $associated;
$result = [];
foreach ($items as $item) {
$titles = [$item[$this->titleAttribute]];
$temp = $item;
while (isset($items[(int)$temp[$this->parentAttribute]])) {
$titles[] = $items[(int)$temp[$this->parentAttribute]][$this->titleAttribute];
$temp = $items[(int)$temp[$this->parentAttribute]];
}
$result[$item[$this->primaryKeyAttribute]] = implode(' - ', array_reverse($titles));
}
return $result;
}
/**
* Returns associated array ($alias=>$fullTitle, $alias=>$fullTitle, ...)
* @param mixed $parent number, object or array of numbers
* @return array
*/
public function getAliasList($parent = 0)
{
$this->cached();
$items = $this->getFullAssocData(
[
$this->aliasAttribute,
$this->titleAttribute,
$this->parentAttribute,
],
$parent
);
$associated = [];
foreach ($items as $item) {
$associated[$item[$this->aliasAttribute]] = $item;
}
$items = $associated;
$result = [];
foreach ($items as $item) {
$titles = [$item[$this->titleAttribute]];
$temp = $item;
while (isset($items[(int)$temp[$this->parentAttribute]])) {
$titles[] = $items[(int)$temp[$this->parentAttribute]][$this->titleAttribute];
$temp = $items[(int)$temp[$this->parentAttribute]];
}
$result[$item[$this->aliasAttribute]] = implode(' - ', array_reverse($titles));
}
return $result;
}
/**
* Returns tabulated array ($id=>$title, $id=>$title, ...)
* @param mixed $parent number, object or array of numbers
* @return array
*/
public function getTabList($parent = 0)
{
$parents = $this->processParents($parent);
$this->cached();
$items = $this->getFullAssocData(
[
$this->primaryKeyAttribute,
$this->titleAttribute,
$this->parentAttribute,
],
$parent
);
$result = [];
foreach ($parents as $parent_id) {
$this->_getTabListRecursive($items, $result, $parent_id);
}
return $result;
}
/**
* @param $items
* @param $result
* @param $parent_id
* @param int $indent
*/
protected function _getTabListRecursive(&$items, &$result, $parent_id, $indent = 0)
{
foreach ($items as $item) {
if ((int)$item[$this->parentAttribute] == (int)$parent_id && !isset($result[$item[$this->primaryKeyAttribute]])) {
$result[$item[$this->primaryKeyAttribute]] = str_repeat('- ', $indent).$item[$this->titleAttribute];
$this->_getTabListRecursive($items, $result, $item[$this->primaryKeyAttribute], $indent + 1);
}
}
}
/**
* Returns tabulated array ($url=>$title, $url=>$title, ...)
* @param mixed $parent number, object or array of numbers
* @param int $sub levels
* @return array
*/
public function getUrlList($parent = 0)
{
$criteria = $this->getOwnerCriteria();
if (!$parent) {
$parent = $this->getOwner()->getPrimaryKey();
}
if ($parent) {
$criteria->compare($this->primaryKeyAttribute, $this->getChildsArray($parent));
}
$items = $this->cached($this->getOwner())->findAll($criteria);
$categories = [];
foreach ($items as $item) {
$categories[(int)$item->{$this->parentAttribute}][] = $item;
}
return $this->_getUrlListRecursive($categories, $parent);
}
/**
* @param $items
* @param $parent
* @param int $indent
* @return array
*/
protected function _getUrlListRecursive($items, $parent, $indent = 0)
{
$parent = (int)$parent;
$resultArray = [];
if (isset($items[$parent]) && $items[$parent]) {
foreach ($items[$parent] as $item) {
$resultArray = $resultArray + [
$item->{$this->urlAttribute} => str_repeat(
'-- ',
$indent
).$item->{$this->titleAttribute},
] + $this->_getUrlListRecursive(
$items,
$item->getPrimaryKey(),
$indent + 1
);
}
}
return $resultArray;
}
/**
* Returns items for zii.widgets.CMenu widget
* @param mixed $parent number, object or array of numbers
* @param int $sub levels
* @return array
*/
public function getMenuList($sub = 0, $parent = 0)
{
$criteria = $this->getOwnerCriteria();
if (!$parent) {
$parent = $this->getOwner()->getPrimaryKey();
}
if ($parent) {
$criteria->compare($this->primaryKeyAttribute, $this->getChildsArray($parent));
}
$items = $this->cached($this->getOwner())->findAll($criteria);
$categories = [];
foreach ($items as $item) {
$categories[(int)$item->{$this->parentAttribute}][] = $item;
}
return $this->_getMenuListRecursive($categories, $parent, $sub);
}
/**
* @param $items
* @param $parent
* @param $sub
* @return array
*/
protected function _getMenuListRecursive($items, $parent, $sub)
{
$parent = (int)$parent;
$resultArray = [];
if (isset($items[$parent]) && $items[$parent]) {
foreach ($items[$parent] as $item) {
$active = $item->{$this->linkActiveAttribute};
$resultArray[$item->getPrimaryKey()] = [
'id' => $item->getPrimaryKey(),
'label' => $item->name,
'count' => $this->statAttribute ? $item->{$this->statAttribute} : 0,
'url' => Yii::app()->createUrl('/store/category/view', ['path' => $item->path]),
'icon' => $this->getAttributeValue('iconAttribute', $item),
'icon_alt' => $this->getAttributeValue('iconAltAttribute', $item),
'icon_title' => $this->getAttributeValue('iconTitleAttribute', $item),
'active' => $active,
'itemOptions' => ['class' => 'item_'.$item->getPrimaryKey()],
'linkOptions' => $active ? ['rel' => 'nofollow'] : [],
] + ($sub ? [
'items' => $this->_getMenuListRecursive(
$items,
$item->getPrimaryKey(),
$sub - 1
),
] : []);
}
}
return $resultArray;
}
private function getAttributeValue($name, $item)
{
if (is_callable($this->{$name})) {
return call_user_func($this->{$name}, $item);
}
return $this->{$name} !== null ? $item->{$this->{$name}} : '';
}
/**
* Finds model by path
* @param string $path
* @return CActiveRecord model
*/
public function findByPath($path)
{
$domens = explode('/', trim($path, '/'));
$model = null;
$criteria = $this->getOwnerCriteria();
if (count($domens) == 1) {
$criteria->mergeWith(
[
'condition' => 't.'.$this->aliasAttribute.'=:alias AND (t.'.$this->parentAttribute.' iS NULL OR t.'.$this->parentAttribute.'=0)',
'params' => [':alias' => $domens[0]],
]
);
$model = $this->cached($this->getOwner())->find($criteria);
} else {
$criteria->mergeWith(
[
'condition' => 't.'.$this->aliasAttribute.'=:alias',
'params' => [':alias' => $domens[0]],
]
);
$parent = $this->cached($this->getOwner())->find($criteria);
if ($parent) {
$domens = array_slice($domens, 1);
foreach ($domens as $alias) {
$model = $parent->getChildByAlias($alias, $this->getOriginalCriteria());
if (!$model) {
return null;
}
$parent = $model;
}
}
}
return $model;
}
/**
* Checks for current model is child of parent
* @param mixed $parent number, object or array of numbers
* @return bool
*/
public function isChildOf($parent)
{
if (is_int($parent) && $this->getOwner()->getPrimaryKey() == $parent) {
return false;
}
$parents = $this->arrayFromArgs($parent);
$model = $this->getOwner();
$i = 50;
while ($i-- && $model) {
if (in_array($model->getPrimaryKey(), $parents)) {
return true;
}
$model = $model->{$this->parentRelation};
}
return false;
}
/**
* Constructs full path for current model
* @param string $separator
* @return string
*/
public function getPath($separator = '/')
{
$uri = [$this->getOwner()->{$this->aliasAttribute}];
$category = $this->getOwner();
$i = 10;
while ($i-- && $this->cached($category)->{$this->parentRelation}) {
$uri[] = $category->{$this->parentRelation}->{$this->aliasAttribute};
$category = $category->{$this->parentRelation};
}
return implode(array_reverse($uri), $separator);
}
/**
* Constructs breadcrumbs for zii.widgets.CBreadcrumbs widget
* @param bool $lastLink if you can have link in last element
* @return array
*/
public function getBreadcrumbs($lastLink = false)
{
if (true === $lastLink) {
$breadcrumbs = [$this->getOwner()->{$this->titleAttribute} => Yii::app()->createUrl('/store/category/view', ['path' => $this->path])];
} else {
$breadcrumbs = [$this->getOwner()->{$this->titleAttribute}];
}
$page = $this->getOwner();
$i = 50;
while ($i-- && $this->cached($page)->{$this->parentRelation}) {
$breadcrumbs[$page->{$this->parentRelation}->{$this->titleAttribute}] = Yii::app()->createUrl('/store/category/view', ['path' => $page->{$this->parentRelation}->path]);
$page = $page->{$this->parentRelation};
}
return array_reverse($breadcrumbs);
}
/**
* Constructs full title for current model
* @param string $separator
* @return string
*/
public function getFullTitle($inverse = false, $separator = ' - ')
{
$titles = [$this->getOwner()->{$this->titleAttribute}];
$item = $this->getOwner();
$i = 50;
while ($i-- && $this->cached($item)->{$this->parentRelation}) {
$titles[] = $item->{$this->parentRelation}->{$this->titleAttribute};
$item = $item->{$this->parentRelation};
}
return implode($inverse ? $titles : array_reverse($titles), $separator);
}
/**
* Optional redeclare this method in your model for use (@link getMenuList())
* or define in (@link requestPathAttribute) your $_GET attribute for url matching
* @return bool true if current request url matches with category path
*/
public function getLinkActive()
{
return mb_strpos(
Yii::app()->request->getParam($this->requestPathAttribute),
$this->getOwner()->path,
null,
'UTF-8'
) === 0;
}
/**
* @param $attributes
* @param int $parent
* @return array|\CDbDataReader
*/
protected function getFullAssocData($attributes, $parent = 0)
{
$criteria = $this->getOwnerCriteria();
$attributes = $this->aliasAttributes(array_unique(array_merge($attributes, [$this->primaryKeyAttribute])));
$criteria->select = implode(', ', $attributes);
if (!$parent) {
$parent = $this->getOwner()->getPrimaryKey();
}
if ($parent) {
$criteria->compare($this->primaryKeyAttribute, array_merge([$parent], $this->getChildsArray($parent)));
}
$command = $this->createFindCommand($criteria);
$this->clearOwnerCriteria();
return $command->queryAll();
}
/**
* @param $parent
* @return array
*/
protected function processParents($parent)
{
if (!$parent) {
$parent = $this->getOwner()->getPrimaryKey();
}
$parents = $this->arrayFromArgs($parent);
return $parents;
}
/**
* @param $items
* @return array
*/
protected function arrayFromArgs($items)
{
$array = [];
if (!$items) {
$items = [0];
} elseif (!is_array($items)) {
$items = [$items];
}
foreach ($items as $item) {
if (is_object($item)) {
$array[] = $item->getPrimaryKey();
} else {
$array[] = $item;
}
}
return array_unique($array);
}
/**
* @param $alias
* @param null $criteria
* @return mixed
*/
protected function getChildByAlias($alias, $criteria = null)
{
if ($criteria === null) {
$criteria = $this->getOwnerCriteria();
}
$criteria->mergeWith(
[
'condition' => 't.'.$this->aliasAttribute.'=:alias AND t.'.$this->parentAttribute.'=:parent_id',
'params' => [
':alias' => $alias,
':parent_id' => $this->getOwner()->getPrimaryKey(),
],
]
);
return $this->cached($this->getOwner())->find($criteria);
}
}