TYPE_NOTAGS, ]; public $langDistricts = [ 'title' => TYPE_NOTAGS, ]; public $langMetroBranches = [ 'title' => TYPE_NOTAGS, ]; public $langMetroStations = [ 'title' => TYPE_NOTAGS, ]; # ---------------------------------------------------------------------------------------------------- # Регионы /** * Region model * @param int|null $id * @param array $columns * @param array $with * @param string|null $lang * @return \bff\modules\geo\models\Region | \bff\db\illuminate\Model |array */ public function region($id = null, $columns = ['*'], array $with = [], $lang = null) { $model = $this->model('Region'); if (! empty($id)) { return $model->one($id, $columns, $with, $lang); } return $model; } public function regionMaxDeep() { return $this->region()->nodeGetMaxDeep(); } /** * @param array $filter * @param array $opts * @return array|int */ public function regionsListing(array $filter, array $opts = []) { $opts = $this->defaults($opts, [ 'countOnly' => false, 'oneArray' => false, 'fields' => [], 'orderBy' => 'numleft', 'limit' => 0, 'offset' => 0, 'cache' => 0, 'keyBy' => '', 'lang' => $this->locale->current(), ]); $lang = $opts['lang']; $search = $filter['search'] ?? ''; unset($filter['search']); $or = $filter['or'] ?? false; // условия добавляемые через or unset($filter['or']); $pidParents = $filter['pid_parents'] ?? false; // для указанных парентов unset($filter['pid_parents']); $regions = $this->region(); $regions->langDefault($lang); $regions = $regions->where($filter); if ($pidParents) { $regions->where(function ($q) use (&$pidParents) { /** @var \bff\modules\geo\models\Region $q */ $pidParents = $this->regionsListing(['id' => $pidParents]); foreach ($pidParents as $v) { $q->orWhere([ ['numleft', '>=', $v['numleft']], ['numright', '<=', $v['numright']], ]); } }); } if ($or) { $regions->where(function ($q) use (&$or) { /** @var \bff\modules\geo\models\Region $q */ foreach ($or as $v) { $q->orWhere($v); } }); } if ($search) { $regions->where(function ($q) use ($search) { /** @var \bff\modules\geo\models\Region $q */ foreach ($this->locale->getLanguages() as $l) { $q->orWhere('title_' . $l, 'LIKE', $search . '%'); } $q->orWhere('title_alt', 'LIKE', '%' . $search . '%'); }); $regions->selectRaw('title_' . $lang . ' LIKE ? AS prim', [$search . '%']); $regions->orderByRaw('prim DESC'); } if ($opts['countOnly']) { return $regions->count(); } $regions->orderByRaw($opts['orderBy']); if ($opts['limit']) { $regions->limit($opts['limit']); } if ($opts['offset']) { $regions->offset($opts['offset']); } $fields = [ 'id', 'pid', 'numlevel', 'numleft', 'numright', 'title', 'keyword', 'city', 'enabled', 'fav', ]; if (! empty($opts['fields'])) { $fields = array_unique(array_merge($fields, $opts['fields'])); } if (($parents = array_search('parents', $fields)) !== false) { unset($fields[$parents]); $parents = true; } $data = $regions->get($fields); if (empty($data)) { return []; } if (! empty($opts['keyBy'])) { $data = $data->keyBy($opts['keyBy']); } $data = $data->toArray(); if (array_search('declension', $fields) !== false) { foreach ($data as & $v) { $v['declension'] = Geo::prepareDeclension($v['declension'], $lang, $v['title']); } unset($v); } if ($parents) { # достанем всех парентов для каждого элемента выборки $parents = []; $need = $data; do { $p = []; foreach ($need as $v) { if (isset($parents[ $v['pid'] ])) { continue; } $p[ $v['pid'] ] = 1; } $need = $this->regionsListing([ 'id' => array_keys($p), ['numlevel', '>', 0], ], ['lang' => $lang]); foreach ($need as $v) { $parents[ $v['id'] ] = $v; } } while (! empty($need)); foreach ($data as & $v) { $r = []; $p = $v['pid']; while (isset($parents[$p])) { $pa = $parents[$p]; $r[ $pa['numlevel'] ] = $pa; $p = $pa['pid']; } $v['parents'] = array_reverse($r, true); } unset($v); } if ($opts['oneArray']) { $data = reset($data); return ! empty($data) ? $data : []; } return $data; } /** * @param $regionID * @param array $opts * @return array */ public function regionChangePidFilter($regionID, array $opts = []) { $filter = []; do { $region = $this->regionsListing(['id' => $regionID], ['oneArray' => true]); if (empty($region)) { $filter['id'] = 0; break; } $city = $opts['city'] ?? $region['city'] ?? 0; $in = $opts['in'] ?? 0; $max = $this->region()->nodeGetMaxDeep(); if ($max && ! $city) { $myMax = (int)$this->region() ->where('numleft', '>', $region['numleft']) ->where('numright', '<', $region['numright']) ->max('numlevel') ; if ($myMax) { $max -= $myMax - $region['numlevel']; } else { $max--; } } $filter = [ 'city' => 0, ['numlevel', '>', 0], ['id', '!=', $regionID], ]; if ($max) { $filter[] = ['numlevel', '<', $max]; } if ($in) { $parents = $this->regionParents($regionID, ['keyBy' => 'numlevel']); $filter[] = ['numleft', '>=', $parents[1]['numleft'] ?? 0 ]; $filter[] = ['numright', '<=', $parents[1]['numright'] ?? 0 ]; } } while (false); return $filter; } /** * @param $regionID * @param null $fav * @return bool */ public function regionFavToggle($regionID, $fav = null) { if (! $regionID) { return false; } $model = $this->region(); $region = $model->find($regionID); if (empty($region)) { return false; } if (! is_null($fav) && ((bool)$region->fav == (bool)$fav)) { return true; } if ($region->fav) { $region->update(['fav' => 0]); } else { $max = (int)$model->max('fav') + 1; $region->update(['fav' => $max]); } return true; } /** * @param int|array $region * @param array $opts * @return array|\bff\db\illuminate\Model[]|\bff\modules\geo\models\Region[]|\Illuminate\Database\Eloquent\Collection|string */ public function regionParents($region, array $opts = []) { $opts = $this->defaults($opts, [ 'separator' => false, 'includeSelf' => true, 'excludeRoot' => true, 'fields' => [], 'toArray' => true, 'keyBy' => '', 'lang' => $this->locale->current(), ]); $data = []; do { if (is_numeric($region)) { $region = $this->region($region); if (empty($region->id)) { break; } $region = $region->toArray(); } if (! is_array($region) || empty($region['id'])) { break; } $includeSelf = $opts['includeSelf']; $regions = $this->region(); $regions->langDefault($opts['lang']); $regions = $regions ->where('numleft', ($includeSelf ? '<=' : '<'), $region['numleft']) ->where('numright', ($includeSelf ? '>=' : '>'), $region['numright']) ; if ($opts['excludeRoot']) { $regions->where('numlevel', '!=', 0); } $fields = [ 'id', 'pid', 'numlevel', 'numleft', 'numright', 'title', 'keyword', 'city', 'enabled', 'fav', ]; if (! empty($opts['fields'])) { $fields = array_unique(array_merge($fields, $opts['fields'])); } $regions->orderBy('numleft'); $data = $regions->get($fields); } while (false); if (empty($data)) { if ($opts['separator']) { return ''; } return []; } if (! empty($opts['separator'])) { $result = []; foreach ($data as $v) { $result[] = $v->title; } return join($opts['separator'], $result); } if (! empty($opts['keyBy'])) { $data = $data->keyBy($opts['keyBy']); } if ($opts['toArray']) { return $data->toArray(); } return $data; } /** * Получение данных о регионе * @param array|int $filter фильтр * @param array $opts * @return mixed */ public function regionData($filter, $opts = []) { if (is_numeric($filter)) { $filter = ['id' => $filter]; } $opts = $this->defaults($opts, [ 'cache' => 0, 'lang' => $this->locale->current(), 'fields' => [], 'oneArray' => true, ]); $lang = $opts['lang']; $fields = [ 'id', 'pid', 'numlevel', 'numleft', 'numright', 'title', 'keyword', 'city', 'enabled', 'fav', 'declension', // 'parents' ]; if (! empty($opts['fields'])) { $fields = array_unique(array_merge($fields, $opts['fields'])); } $regions = $this->region(); $regions->langDefault($lang); $regions = $regions->where($filter); $regions->limit(1); $data = $regions->get($fields); if (empty($data)) { return []; } $data = $data->toArray(); $data = reset($data); if (empty($data)) { return []; } if (empty($data['numlevel'])) { return []; } $data['declension'] = Geo::prepareDeclension($data['declension'], $lang, $data['title']); $data['parents'] = $this->regionParents($data, [ 'lang' => $lang, 'includeSelf' => false, 'keyBy' => 'numlevel', ]); return $data; } # --------------------------------------------------------------------------------- # Районы /** * District model * @param int|null $id * @param array $columns * @param array $with * @param string|null $lang * @return \bff\modules\geo\models\District | \bff\db\illuminate\Model |array */ public function district($id = null, $columns = ['*'], array $with = [], $lang = null) { $model = $this->model('District'); if (! empty($id)) { return $model->one($id, $columns, $with, $lang); } return $model; } /** * @param array $filter * @param array $opts * @return array|\bff\db\illuminate\Model|\bff\modules\geo\models\District|int|mixed */ public function districtsListing(array $filter, array $opts = []) { $opts = $this->defaults($opts, [ 'countOnly' => false, 'oneArray' => false, 'fields' => [], 'orderBy' => 'id', 'limit' => 0, 'offset' => 0, 'keyBy' => '', 'lang' => $this->locale->current(), ]); $districts = $this->district(); $districts->langDefault($opts['lang']); $districts = $districts->where($filter); if ($opts['countOnly']) { return $districts->count(); } if ($opts['limit']) { $districts->limit($opts['limit']); } if ($opts['offset']) { $districts->offset($opts['offset']); } $districts->orderByRaw($opts['orderBy']); $fields = ['id', 'city_id', 'title', ]; if (! empty($opts['fields'])) { $fields = array_unique(array_merge($fields, $opts['fields'])); } $data = $districts->get($fields); if (empty($data)) { return []; } if ($opts['oneArray']) { $data = $data->toArray(); $data = reset($data); return ! empty($data) ? $data : []; } $data = $data->sortBy('title'); if (! empty($opts['keyBy'])) { $data = $data->keyBy($opts['keyBy']); } return $data->toArray(); } # --------------------------------------------------------------------------------- # Метро /** * MetroBranch model * @param int|null $id * @param array $columns * @param array $with * @param string|null $lang * @return \bff\modules\geo\models\MetroBranch | \bff\db\illuminate\Model |array */ public function metroBranch($id = null, $columns = ['*'], array $with = [], $lang = null) { $model = $this->model('MetroBranch'); if (! empty($id)) { return $model->one($id, $columns, $with, $lang); } return $model; } /** * @param array $filter * @param array $opts * @return array|\bff\db\illuminate\Model|\bff\modules\geo\models\MetroBranch|int|mixed */ public function metroBranchesListing(array $filter, array $opts = []) { $opts = $this->defaults($opts, [ 'countOnly' => false, 'oneArray' => false, 'fields' => [], 'orderBy' => 'num', 'limit' => 0, 'offset' => 0, 'keyBy' => '', 'lang' => $this->locale->current(), ]); $branches = $this->metroBranch(); $branches->langDefault($opts['lang']); $branches = $branches->where($filter); if ($opts['countOnly']) { return $branches->count(); } if ($opts['limit']) { $branches->limit($opts['limit']); } if ($opts['offset']) { $branches->offset($opts['offset']); } $branches->orderByRaw($opts['orderBy']); $fields = ['id', 'title', 'color']; if (! empty($opts['fields'])) { $fields = array_unique(array_merge($fields, $opts['fields'])); } $data = $branches->get($fields); if (empty($data)) { return []; } if (! empty($opts['keyBy'])) { $data = $data->keyBy($opts['keyBy']); } $data = $data->toArray(); if ($opts['oneArray']) { $data = reset($data); return ! empty($data) ? $data : []; } return $data; } /** * MetroStation model * @param int|null $id * @param array $columns * @param array $with * @param string|null $lang * @return \bff\modules\geo\models\MetroStation | \bff\db\illuminate\Model |array */ public function metroStation($id = null, $columns = ['*'], array $with = [], $lang = null) { $model = $this->model('MetroStation'); if (! empty($id)) { return $model->one($id, $columns, $with, $lang); } return $model; } /** * @param array $filter * @param array $opts * @return array|\bff\db\illuminate\Model|\bff\modules\geo\models\MetroBranch|int|mixed */ public function metroStationsListing(array $filter, array $opts = []) { $opts = $this->defaults($opts, [ 'countOnly' => false, 'oneArray' => false, 'fields' => [], 'orderBy' => '', 'limit' => 0, 'offset' => 0, 'keyBy' => '', 'lang' => $this->locale->current(), ]); $pid = null; if (isset($filter['pid'])) { $pid = $filter['pid']; unset($filter['pid']); } $search = $filter['search'] ?? ''; unset($filter['search']); $stations = $this->metroStation(); $stations->langDefault($opts['lang']); $query = $stations->where($filter); if (! is_null($pid)) { $in = $this->metroStationIn()->getTable(); if ($pid > 0) { $query ->join($in, 'id', '=', 'station_id') ->where($in . '.branch_id', $pid) ; } else { $query ->leftJoin($in, 'id', '=', 'station_id') ->whereNull($in . '.branch_id') ; } if (empty($opts['orderBy'])) { $opts['orderBy'] = $in . '.num'; } } if ($search) { $query->where(function ($q) use ($search) { /** @var \bff\modules\geo\models\MetroStation $q */ foreach ($this->locale->getLanguages() as $lang) { $q->orWhere('title_' . $lang, 'LIKE', '%' . $search . '%'); } }); } if ($opts['countOnly']) { return $query->count(); } if ($opts['oneArray']) { $opts['limit'] = 1; } if ($opts['limit']) { $query->limit($opts['limit']); } if ($opts['offset']) { $query->offset($opts['offset']); } if (empty($opts['orderBy'])) { $opts['orderBy'] = 'id'; } $query->orderByRaw($opts['orderBy']); $fields = ['id', 'title', 'nearby']; if (! empty($opts['fields'])) { $fields = array_unique(array_merge($fields, $opts['fields'])); } if ($alsoin = array_search('alsoin', $fields)) { unset($fields[$alsoin]); } $data = $query->get($fields); if (empty($data)) { return []; } if (! empty($opts['keyBy'])) { $data = $data->keyBy($opts['keyBy']); } $data = $data->toArray(); if ($alsoin) { $alsoin = []; foreach ($data as $v) { $alsoin[] = $v['id']; } $in = $this->metroStationIn()->getTable(); $alsoin = $this->metroBranch() ->join($in, 'id', '=', 'branch_id') ->whereIn($in . '.station_id', $alsoin) ; if ($pid > 0) { $alsoin->where('id', '!=', $pid); } $alsoin = $alsoin ->orderBy('num') ->get(['id', 'title', 'color', 'num', $in . '.station_id']) ->toArray(); foreach ($data as & $v) { $v['alsoin'] = []; foreach ($alsoin as $vv) { if ($vv['station_id'] != $v['id']) { continue; } $v['alsoin'][$vv['id']] = $vv; } } unset($v); } if ($opts['oneArray']) { $data = reset($data); return ! empty($data) ? $data : []; } return $data; } /** * MetroStationIn model * @param int|null $id * @param array $columns * @param array $with * @param string|null $lang * @return \bff\modules\geo\models\MetroStation | \bff\db\illuminate\Model |array */ public function metroStationIn($id = null, $columns = ['*'], array $with = [], $lang = null) { $model = $this->model('MetroStationIn'); if (! empty($id)) { return $model->one($id, $columns, $with, $lang); } return $model; } /** * @param $cityID * @return array */ public function metroList($cityID) { $result = []; do { $branches = $this->metroBranchesListing(['city_id' => $cityID], ['keyBy' => 'id']); if (empty($branches)) { break; } $stations = $this->metroStationsListing(['city_id' => $cityID], ['keyBy' => 'id']); if (empty($stations)) { break; } $in = $this->metroStationIn() ->whereIn('branch_id', array_keys($branches)) ->orderBy('num') ->get() ->toArray(); foreach ($branches as $v) { $v['st'] = []; foreach ($in as $vv) { if ($vv['branch_id'] != $v['id']) { continue; } if (! isset($stations[ $vv['station_id'] ])) { continue; } $s = $stations[ $vv['station_id'] ]; $v['st'][ $s['id'] ] = $s; } $result[ $v['id'] ] = $v; } } while (false); return $result; } public function getLocaleTables() { return [ static::TABLE_REGIONS => ['type' => 'fields', 'fields' => $this->langRegions, 'title' => _t('geo', 'Countries / regions / cities'), 'translatable-data' => true], // 'fields-serialized' => array('declension'), static::TABLE_REGIONS_DISTRICTS => ['type' => 'fields', 'fields' => $this->langDistricts, 'title' => _t('geo', 'Areas'), 'translatable-data' => true], static::TABLE_REGIONS_METRO_BRANCHES => ['type' => 'fields', 'fields' => $this->langMetroBranches, 'title' => _t('geo', 'Metro Branches'), 'translatable-data' => true], static::TABLE_REGIONS_METRO_STATIONS => ['type' => 'fields', 'fields' => $this->langMetroStations, 'title' => _t('geo', 'Metro Stations'), 'translatable-data' => true], ]; } }