_items = is_array($items) ? new ObjectArray($items) : $items; } /** * Добавляет список элементов. * * @param ObjectArray|array $items Список элементов меню. * * @return void */ public function AddItems (ObjectArray|array $items): void { // Получаю список $itemsToAdd = is_array($items) ? new ObjectArray($items) : $items; /** * @var MenuItemModel $item Элемент меню */ foreach ($itemsToAdd as $item) // - добавляю элемент $this->AddItem($item); } /** * Добавляет элемент меню в список. * * @param MenuItemModel $item Элемент меню. * * @return void */ public function AddItem (MenuItemModel $item): void { // Добавляю $this->_items[] = $item; } /** * Содержится ли элемент в списке. * * @param string $id Идентификатор элемента меню. * * @return bool Содержится ли элемент в списке. */ public function ContainsItem (string $id): bool { // Проверяю, что идентификатор не пустой if (GUIDExtension::IsNotValidOrEmpty($id)) // - возвращаю отрицательный результат return false; // Получаю, что элемент с таким идентификатором существует return $this->_items->IsExist(fn (MenuItemModel $item) => $item->Id == $id); } /** * Вычисляет количество элементов меню. * * @param callable|null $predicate Выражение, уточняющее детали. * * @return int Количество элементов меню. */ public function Count (?callable $predicate = null): int { return $this->_items->Count($predicate); } /** * Очищает список элементов. * * @return void */ public function Clear (): void { // Очищаю $this->_items->Clear(); } /** * Перемещает элемент меню вверх. * * @param string $id Идентификатор элемента меню. * * @return ActionState Результат выполнения. */ public function MoveUp (string $id): ActionState { // Создаю результат $result = new ActionState(); // Проверяю, что идентификатор не пустой if (GUIDExtension::IsNotValidOrEmpty($id)) { // - то выдаю ошибку $result->AddError("Неверный идентификатор (GUID)"); // - и прерываю return $result; } // Загружаю элемент $item = $this->GetItem($id); // Если элемент не загружен if ($item == null) { // - то выдаю ошибку $result->AddError("Пункт меню с идентификатором 0%s не найден!"); // - и прерываю return $result; } // Получаю его текущий порядковый номер $oldOrder = $item->Order; // Если порядковый номер не больше 1 if ($oldOrder <= 1) { // - то выдаю предупреждение $result->AddWarning("Пункт меню находится на первом месте. Перемещение не требуется!"); // - и прерываю return $result; } // Получаю новый порядковый номер $newOrder = $oldOrder - 1; // Получаю элемент, находящийся на новом порядковом номере (или GuidExEmpty, если нет элемента) $oldPlaceItem = $this->_items->GetRow(fn (MenuItemModel $item) => $item->Order == $newOrder); // Присваиваю новый порядковый номер изменяемого элемента $item->Order = $newOrder; // Обновляю список элементов $this->UpdateItem($item); // И если на новом порядковом номере существовал элемент if ($oldPlaceItem !== false) { // - то присваиваю ему старый порядковый номер $oldPlaceItem->Order = $oldOrder; // - и обновляю список $this->UpdateItem($oldPlaceItem); } // Возвращаю результат return $result; } /** * Получает элемент меню. * * @param string $id Идентификатор элемента меню. * * @return MenuItemModel|null Элемент меню или null, если элемент не найден. */ public function GetItem (string $id): ?MenuItemModel { // Проверяю, что идентификатор не пустой if (GUIDExtension::IsNotValidOrEmpty($id)) // - то возвращаю null return null; // Получаю элемент $item = $this->_items->GetRow(fn (MenuItemModel $item) => $item->Id == $id); // Если элемент не найден, то возвращаю null, иначе возвращаю найденный элемент. return ($item === false) ? null : $item; } /** * Обновляет элемент меню. * * @param MenuItemModel $item Обновлённый элемент меню. * * @return void */ public function UpdateItem (MenuItemModel $item): void { // Если элемент существует if ($this->_items->IsExist(fn (MenuItemModel $itemM) => $itemM->Id == $item->Id)) // - то удаляю его $this->RemoveItem($item->Id); // Добавляю новый $this->AddItem($item); } /** * Удаление элемента меню. * * @param string $id Идентификатор элемента меню. * * @return bool Статус удаления. */ public function RemoveItem (string $id): bool { // Проверяю, что идентификатор не пустой if (GUIDExtension::IsNotValidOrEmpty($id)) // - возвращаю отрицательный результат return false; return $this->_items->Delete(fn (MenuItemModel $item) => $item->Id == $id); } /** * Перемещает элемент меню вниз. * * @param string $id Идентификатор элемента меню. * * @return ActionState Результат выполнения. */ public function MoveDown (string $id): ActionState { // Создаю результат $result = new ActionState(); // Проверяю, что идентификатор не пустой if (GUIDExtension::IsNotValidOrEmpty($id)) { // - то выдаю ошибку $result->AddError("Неверный идентификатор (GUID)"); // - и прерываю return $result; } // Загружаю элемент $item = $this->GetItem($id); // Если элемент не загружен if ($item == null) { // - то выдаю ошибку $result->AddError("Пункт меню с идентификатором 0%s не найден!"); // - и прерываю return $result; } // Получаю его текущий порядковый номер $oldOrder = $item->Order; // Если порядковый номер не больше 1 if ($oldOrder >= $this->GetLastItemOrder($item->ParentId)) { // - то выдаю предупреждение $result->AddWarning("Пункт меню находится на последнем месте. Перемещение не требуется!"); // - и прерываю return $result; } // Получаю новый порядковый номер $newOrder = $oldOrder + 1; // Получаю элемент, находящийся на новом порядковом номере (или GuidExEmpty, если нет элемента) $oldPlaceItem = $this->_items->GetRow(fn (MenuItemModel $item) => $item->Order == $newOrder); // Присваиваю новый порядковый номер изменяемого элемента $item->Order = $newOrder; // Обновляю список элементов $this->UpdateItem($item); // И если на новом порядковом номере существовал элемент if ($oldPlaceItem !== false) { // - то присваиваю ему старый порядковый номер $oldPlaceItem->Order = $oldOrder; // - и обновляю список $this->UpdateItem($oldPlaceItem); } // Возвращаю результат return $result; } /** * Получает порядковый номер последнего элемента в списке. * * @param string $parentItemId Идентификатор родителя. * * @return int Порядковый номер последнего элемента в списке. */ public function GetLastItemOrder (string $parentItemId): int { // Проверяю, что идентификатор не пустой if (GUIDExtension::IsNotValidOrEmpty($parentItemId) && $parentItemId !== GUIDExtension::GUIDEmpty) // - возвращаю отрицательный результат return 0; // Получаю последний элемент $item = $this->GetSubItems($parentItemId)->Last(false); // Если элемент не найден if ($item === false) // - возвращаю нулевой результат return 0; // Возвращаю порядок последнего элемента return $item->Order; } /** * Получает список вложенных элементов. * * @param string $parentItemId Идентификатор родителя. * @param bool $sortByOrder Нужно ли сортировать по порядку элементов. * * @return ObjectArray Список вложенных элементов. */ public function GetSubItems (string $parentItemId, bool $sortByOrder = true): ObjectArray { // Проверяю, что идентификатор корректен if (GUIDExtension::IsNotValidOrEmpty($parentItemId) && $parentItemId !== GUIDExtension::GUIDEmpty) // - возвращаю отрицательный результат return new ObjectArray([]); // Получаю список элементов $result = $this->_items->GetRows(fn (MenuItemModel $item) => $item->ParentId == $parentItemId); // Если нужно сортировать if ($sortByOrder) // - сортирую $result->Sort("Order"); // Возвращаю результат return $result; } /** * Изменяет родителя у элемента. * * @param string $id Идентификатор элемента. * @param string $newParentId Идентификатор нового родителя. * * @return ActionState Результат выполнения. */ public function ChangeParent (string $id, string $newParentId): ActionState { // Создаю результат $result = new ActionState(); // Проверяю, что идентификатор не пустой if (GUIDExtension::IsNotValidOrEmpty($id) || GUIDExtension::IsNotValidOrEmpty($newParentId)) { // - то выдаю ошибку $result->AddError("Неверный идентификатор (GUID)"); // - и прерываю return $result; } // Если имеет дочерние if ($this->HasSubItems($id)) { // - то выдаю ошибку $result->AddError( "Пункт меню с идентификатором 0%s имеет дочерние пункты (подпункты). Пожалуйста, сперва переместите их, а потом будет возможно сменить родителя у данного элемента!"); // - и прерываю return $result; } // Загружаю элемент $item = $this->GetItem($id); // Если элемент не загружен if ($item == null) { // - то выдаю ошибку $result->AddError("Пункт меню с идентификатором 0%s не найден!"); // - и прерываю return $result; } // Получаю список свободных порядковых номеров $freeOrdersList = $this->GetFreeOrders($newParentId); // Получаю первый свободный порядковый номер $newOrder = count($freeOrdersList) > 0 ? min($freeOrdersList) : $this->GetLastItemOrder($newParentId) + 1; // Присваиваю элементу идентификатор нового родителя $item->ParentId = $newParentId; // Устанавливаю его порядок $item->Order = $newOrder; // Обновляю список элементов $this->UpdateItem($item); // Возвращаю результат return $result; } /** * Проверяет, имеет ли элемент дочерние. * * @param string $id Идентификатор элемента меню. * * @return bool Имеет ли элемент дочерние. */ public function HasSubItems (string $id): bool { // Проверяю, что идентификатор не пустой if (GUIDExtension::IsNotValidOrEmpty($id)) // - возвращаю отрицательный результат return false; // Проверяю, существует ли элемент, у которого идентификатор родителя совпадает // с идентификатором указанного элемента return $this->_items->IsExist(fn (MenuItemModel $item) => $item->ParentId == $id); } /** * Получает порядковый номер отсутствующего элемента в списке между первым и последним. * * @param string $parentItemId Идентификатор родителя. * * @return array Список порядковых номеров, которые не используются. */ public function GetFreeOrders (string $parentItemId): array { // Проверяю, что идентификатор не пустой if (GUIDExtension::IsNotValidOrEmpty($parentItemId) && $parentItemId !== GUIDExtension::GUIDEmpty) // - возвращаю отрицательный результат return []; // Получаю список элементов $items = $this->GetSubItems($parentItemId); // Создаю список-результат $result = []; // Получаю минимальный порядок элементов $min = $this->GetFirstItemOrder($parentItemId); // Получаю максимальный порядок элементов $max = $this->GetLastItemOrder($parentItemId); // Если произошёл уникальный случай, когда нет элементов if ($min == 0 || $max == 0) return $result; // Заношу в список все до минимума if ($min > 0) for ($ind = 1; $ind < $min; $ind++) $result[] = $ind; // Прохожу "занятые" элементы for ($ind = $min; $ind <= $max; $ind++) // - и проверяю их на свободные if (!$items->IsExist(fn (MenuItemModel $item) => $item->Order == $ind)) // -- заношу свободные в список $result[] = $ind; // Сортирую список sort($result, SORT_NUMERIC); // Возвращаю результат return $result; } /** * Получает порядковый номер первого элемента в списке. * * @param string $parentItemId Идентификатор родителя. * * @return int Порядковый номер первого элемента в списке. */ public function GetFirstItemOrder (string $parentItemId): int { // Проверяю, что идентификатор не пустой if (GUIDExtension::IsNotValidOrEmpty($parentItemId) && $parentItemId !== GUIDExtension::GUIDEmpty) // - возвращаю отрицательный результат return 0; // Получаю первый элемент $item = $this->GetSubItems($parentItemId)->First(false); // Если элемент не найден if ($item === false) // - возвращаю нулевой результат return 0; // Возвращаю порядок первого элемента return $item->Order; } /** * @inheritdoc */ public function getIterator (): Traversable { return $this->_items->getIterator(); } /** * @inheritdoc */ public function Duplicate (): MenuItems { return new MenuItems($this->_items); } /** * @inheritdoc */ public function Serialize (): string { return $this->_items->Serialize(); } /** * @inheritdoc */ public function UnSerialize (string $serialized): void { // Создаю новый класс списка элементов $this->_items = new ObjectArray([]); // Восстанавливаю данные $this->_items->UnSerialize($serialized); } }