public function write() { $pdo = $this->database->getConnection(); $tables = $pdo->select(sprintf("SELECT TABLE_NAME as `table`, COLUMN_NAME as pk FROM information_schema.columns WHERE table_schema = '%s' AND COLUMN_KEY = 'PRI'", $pdo->getDatabaseName())); foreach ($tables as $table) { $name = $table->table; if (!$this->resolver->getModel($name)) { $path = sprintf('%s/app/Model/%s.php', $this->bootLoader->getBaseDir(), ucfirst(Str::camel(Str::singular("{$name}")))); if (!file_exists($path)) { $this->writer->write($path, ['class' => $this->utils->filename($path), 'table' => $table->table, 'pk' => $table->pk], 'Created new model'); } } } }
protected function setUp() { define('MODEL_DIR', '\\Test\\Model'); //define('DEBUG_MODE', true); $this->injector = new Injector(); $this->injector->share(Database::class); $this->database = $this->injector->make('Minute\\Database\\Database', [true]); try { $this->pdo = $this->database->getPdo(); } catch (\PDOException $e) { $this->markTestSkipped('Database connection error: ' . $e->getMessage()); } parent::setUp(); }
public function modelToJsClasses($parent, string $foreignKey = '') { /** @var ModelEx $model */ $self = $parent['self']; $template = ''; if ($class = $this->resolver->getModel($self['name'])) { $model = new $class(); $localKey = $model->getKeyName(); $template .= $this->modelJs->createItem($self['alias'], $parent['children'], $this->database->getColumns($model->getTable())); $template .= $this->modelJs->createItemArray($self['alias'], $class, $localKey, $foreignKey); foreach ($parent['children'] ?? [] as $child) { $template .= $this->modelToJsClasses($child, $localKey); } } return $template; }
/** * Bind listeners defined in plugins, app and Database */ public function bind() { $listeners = $this->cache->get('app-listeners', function () { foreach ($this->resolver->getListeners() as $file) { try { $binding = $this; require_once $file; } catch (\Throwable $e) { $this->logger->warn("Unable to include {$file}: " . $e->getMessage()); } } $listeners = $this->getListeners(); if ($this->database->isConnected()) { /** @var ModelEx $eventModel */ if ($eventModel = $this->resolver->getModel('Event', true)) { try { foreach ($eventModel::all() as $item) { $attrs = $item->attributesToArray(); list($class, $func) = @explode('@', $attrs['handler']); $event = array_merge($attrs, ['event' => $attrs['name'], 'handler' => [sprintf('\\%s', ltrim($class, '\\')), $func ?? 'index']]); $listeners[] = $event; } } catch (\Exception $e) { } } } return $listeners; }, 300); foreach ($listeners as $listener) { $this->dispatcher->listen($listener['event'], $listener['handler'], $listener['priority'] ?? 99, $listener['data'] ?? ''); } }
public function getTargetUserIds(int $ar_list_id) : array { return $this->qCache->get("ar-list-id-{$ar_list_id}", function () use($ar_list_id) { $results = ['positive' => [], 'negative' => []]; if ($list = MArList::find($ar_list_id)) { $sqls = MArListSql::where('ar_list_id', '=', $ar_list_id)->get(); foreach ($sqls as $sql) { $users = $this->qCache->get(md5($sql->sql), function () use($sql) { foreach ($this->db->getPdo()->query($sql->sql) as $row) { $results[] = $row[0]; } return $results ?? []; }); $results[$sql->type] = array_merge($results[$sql->type], $users); } } return array_diff($results['positive'], $results['negative']) ?: []; }, 3600); }
public function setup(HttpRequestEx $request) { $params = $request->getParameters(); try { if (!empty($params['db']['database']) && !empty($params['db']['username']) && !empty($params['db']['password'])) { try { $conn = $this->database->connect($params['db']); if ($pdo = $conn->getPdo()) { $conf = sprintf('%s/app/Config/db-config', $this->bootLoader->getBaseDir()); if (file_put_contents($conf, sprintf('mysql://%s:%s@%s/%s', $params['db']['username'], $params['db']['password'], $params['db']['host'], $params['db']['database']))) { if ($this->installer->install(['minutephp/site'], 'require', true)) { $sth = $pdo->prepare('REPLACE INTO users SET email = :email, password = :password, ip_addr = :ip, created_at = NOW(), updated_at = NOW(), first_name = "Admin", verified = "true"'); $sth->execute(['email' => sprintf('admin@%s', $params['site']['domain'] ?? 'localhost'), 'password' => password_hash(Str::random(), PASSWORD_DEFAULT), 'ip' => $this->sniffer->getUserIP()]); if ($admin_id = $pdo->lastInsertId()) { $sth = $pdo->prepare('REPLACE INTO m_user_groups set user_id = :user_id, group_name = "admin", created_at = NOW(), updated_at = NOW(), expires_at = "20200101", credits = 999, comments = "First run"'); $sth->execute(['user_id' => $admin_id]); $types = ['public' => $params['site'] ?? [], 'private' => []]; foreach ($types as $type => $data) { $sth = $pdo->prepare('REPLACE INTO m_configs set type = :type, data_json = :data'); $sth->execute(['type' => $type, 'data' => json_encode($data)]); } $this->session->startSession($admin_id); return 'pass'; } } else { throw new FirstRunError($this->lang->getText("Unable to run composer")); } } } } catch (\Throwable $e) { throw new FirstRunError($this->lang->getText("Unable to connect to database.\n") . $e->getMessage()); } } throw new FirstRunError($this->lang->getText('All connection parameters are required. Please check connection details')); } catch (\Throwable $e) { if (!empty($conf) && file_exists($conf)) { @unlink($conf); } throw new FirstRunError("Error: " . $e->getMessage()); } }
public function update(UserUpdateDataEvent $event) { if ($user = $event->getUser()) { $fields = array_diff($this->database->getColumns($user->getTable()), ['user_id', 'ident', 'created_at', 'updated_at']); $userDataModel = $this->resolver->getModel('UserData', true); foreach ($event->getNewData() as $key => $value) { if (in_array($key, $fields)) { if ($event->isOverwrite() || empty($user->{$key}) || $key === 'verified' && $value === 'true') { $dataChanged = $dataChanged ?? $user->{$key} !== $value; $user->{$key} = $key === 'password' ? password_hash($value, PASSWORD_DEFAULT) : $value; } } elseif (!empty($userDataModel)) { /** @var MUserDatum $extra */ $extra = $userDataModel::firstOrCreate(['user_id' => $user->user_id, 'key' => $key]); if ($event->isOverwrite() || empty($extra->data)) { if (!empty($value)) { $extra->data = $value; $extra->save(); } else { $extra->delete(); } } } else { $event->setError('INVALID_FIELD'); throw new UserUpdateDataError("Field {$key} not found in users table"); } } if (!empty($dataChanged)) { if ($user->save()) { $event->setHandled(true); } } else { $event->setHandled(true); } } else { $event->setError('INVALID_USER'); } }
public function importSession(ViewEvent $event) { if (!$this->database->isConnected()) { return; } /** @var RouteEx $route */ $view = $event->getView(); $vars = $view->getVars(); $route = $view->get('_route'); $data = $this->getCachedSessionData(false); $data['request'] = array_merge(['url' => getenv('REQUEST_URI')], $this->request->getParameters()); $data['params'] = array_diff_key($route->getDefaults(), array_flip(['controller', 'auth', 'models', '_route'])); foreach ($vars as $key => $value) { if ($key[0] !== '_' && (is_scalar($value) || is_array($value))) { $data['vars'][$key] = $value; } } if (!empty($data['site'])) { $data['site']['version'] = $this->database->hasRdsAccess() ? 'production' : 'debug'; } $printer = sprintf('<script' . '>Minute.setSessionData(%s)</script>', json_encode($data)); $event->setContent($printer); }
/** * Automatically fills fields like created_at, updated_at, *_slug, *_safe * and sets unset fields to null * * @param ModelEx $model * * @return ModelEx */ public function fillMissing($model) { if ($columns = $this->database->getColumns($model->getTable())) { foreach ($columns as $column) { if (!$model->{$column} && $model->{$column} !== 0) { if (preg_match('/^(created_at|updated_at)$/', $column)) { $model->{$column} = Carbon::now(); } elseif (preg_match('/(\\w+)\\_slug$/', $column, $matches)) { if ($root = $model->{$matches[1]} ?? null) { $pk = $model->getKeyName(); $slug = $copy = (new Slugify())->slugify($root); $count = 1; while ($record = $model::where($column, '=', $slug)->first()) { if ($record->{$pk} == $model->{$pk}) { $slug = false; break; } $slug = sprintf('%s-%d', $copy, $count++); } if (!empty($slug)) { $model->{$column} = $slug; } } } elseif ($model->isNullable($column)) { $model->{$column} = null; } } if (preg_match('/\\_safe$/', $column)) { $model->{$column} = Htmlawed::filter($model->{$column}); } elseif (preg_match('/\\_json$/', $column) && is_array($model->{$column})) { $model->{$column} = json_encode($model->{$column}); } } } return $model; }
public function launch() { global $conn; $conn = $this->database->getDsn(); include_once $this->adminer; }
public function getConfiguration() { $conn = ['environments' => ['default_database' => 'dev', "default_migration_table" => "m_phinx_logs", 'dev' => ['name' => $this->database->getDsn()['database'], 'connection' => $this->database->getPdo()]]]; $paths = ['paths' => ['migrations' => __DIR__ . '/db/migrations']]; return array_merge($paths, $conn); }
/** * Find the matching route and execute the controller * * Algorithm - http://www.minutephp.com/ADD * # Get the URL and Method from the event * Find the matching route by URL and method * Get the controller, auth and list of models from route * Throw error if the user is not authorized to access the route * * If Method is GET * Parse the models to create parent child relations * Throw error if the user isn't allowed to Read the model * Create a list of constraints using \ * \ conditions in the route model definitions * \ conditions added using `addConstraint` method * Perform additional checks if permission is SAME_USER * If the controller is null guess it from the route URL * If this is an ajax request with metadata then override the controller with our own GET handler * Call the controller with the constraints using an event * * If Method is POST * If this is an ajax request and we have model definitions * Parse the models in the route * Get the model, and items and type of operation from the request * Throw error if the user cannot the operation on selected model * Create models instances for each item * Call the GenericPostHandler with the instances using an event * end. * * @param RequestEvent $requestEvent * * @throws AuthError * @throws ModelError * @throws RouteError * @throws ValidationError */ public function handle(RequestEvent $requestEvent) { $request = $requestEvent->getRequest(); //try { $parents = null; $method = $request->getMethod(); $this->compile(); //Since we can have multiple POST requests for a single Url if ($method === 'POST' && ($alias = $request->getParameter('alias')) && ($class = $request->getParameter('model'))) { $newCollection = new RouteCollection(); foreach (['alias' => $alias, 'name' => $class] as $key => $value) { /** @var RouteEx $routeEx */ foreach ($this->routeCollection as $name => $routeEx) { if (in_array('POST', $routeEx->getMethods())) { $parents = $routeEx->parsePostModels(); if ($filtered = array_filter($parents, function ($m) use($key, $value) { return $m[$key] === $value; })) { $newCollection->add($name, $routeEx); } } } } if ($newCollection->count()) { $this->routeCollection = $newCollection; } else { throw new ResourceNotFoundException("No Post handler configured for alias '{$alias}' or class '{$class}'"); } } try { $route = $this->match($method, $request->getPath()); } catch (ResourceNotFoundException $e) { $event = new RouterEvent($method, $request->getPath()); $this->dispatcher->fire(RouterEvent::ROUTER_GET_FALLBACK_RESOURCE, $event); if (!($route = $event->getRoute())) { throw $e; } } $event = new AuthEvent($route->getDefault('auth')); $this->lastMatchingRoute = $route; $this->dispatcher->fire(AuthEvent::AUTH_CHECK_ACCESS, $event); if ($event->isAuthorized()) { $user_id = $event->getActiveUserId(); $controller = $controllerArgs = null; $contentType = 'html'; if ($method === 'POST') { $parents = $route->parsePostModels(); $controller = $route->getDefault('controller'); $matches = []; if (empty($controller)) { if (!empty($parents)) { $controller = 'Generic/DefaultPostHandler.php'; } else { $controller = trim(Str::camel(str_replace(' ', '', ucwords(preg_replace('/(\\W+)/', '\\1 ', $route->getPath())))), '/'); } } if (!empty($parents)) { if ($alias = $request->getParameter('alias')) { $matches = array_filter($parents, function ($f) use($alias) { return $f['alias'] === $alias; }); } if (count($matches) === 1) { $model = array_shift($matches); } else { if ($class = $request->getParameter('model')) { $matches = array_filter($parents, function ($f) use($class) { return $f['name'] === $class; }); } if (count($matches) === 1) { $model = array_shift($matches); } else { throw new ModelError("Post route does not have a matching model for alias:'{$alias}' or class:'{$class}'"); } } /** @var ModelEx $inst */ if ($modelClass = $this->resolver->getModel($model['name'])) { $inst = new $modelClass(); $cmd = $request->getParameter('cmd', 'save'); $pri = $inst->getKeyName(); $items = $request->getParameter('items', []); $cols = $this->database->getColumns($inst->getTable()); if (!empty($items)) { foreach ((array) $items as $item) { $accessMode = $cmd == 'save' ? !empty($item[$pri]) ? 'update' : 'create' : 'delete'; $permission = call_user_func([$route, sprintf('get%sPermission', ucwords($accessMode))], $model['alias']); if ($this->canAccessModel($permission, $event->isLoggedInUser())) { $fields = $model['fields'] ?? $cols; $immutable = [$pri, 'user_id']; $fields = array_diff($fields, $immutable); $has_uid = in_array('user_id', $cols); if (count(array_diff(array_keys($item), array_merge($fields, $immutable))) !== 0) { throw new ValidationError(sprintf("Field restriction on '%s' failed on fields: '%s'", $model['alias'], join(', ', array_keys($item)))); } if ($accessMode === 'create') { /** @var ModelEx $m */ $modelClass::unguard(); $instance = new $modelClass($item); } else { if ($instance = $modelClass::where($pri, '=', $item[$pri])->first()) { if ($accessMode !== 'delete') { foreach ($fields as $field) { $instance->{$field} = $item[$field] ?? $instance->{$field} ?? null; } } } else { throw new ModelError(sprintf("No record found in %s for '%s' = '%s'", $model['name'], $pri, $item[$pri])); } } if ($permission == Permission::SAME_USER) { if (!$instance->user_id) { $instance->user_id = $user_id; } else { if ($instance->user_id !== $user_id) { throw new ModelError(sprintf("%s user_id does not match to current user.", ucfirst($model['name']))); } } } if ($has_uid && $accessMode !== 'delete') { $instance->user_id = $instance->user_id ?: ($user_id ?: ($item['user_id'] ?: 0)); } $models[] = $instance; } else { throw new ModelError(sprintf("%s access denied to '%s' in %s. Enable auth or change read permission", ucwords($accessMode), $model['alias'], $route->getPath())); } } } } else { throw new RouteError(sprintf("Cannot create model for class: %s in %s", $model['name'], $route->getPath())); } } $controllerArgs = ['_parents' => $parents, '_models' => $models ?? null, '_mode' => $accessMode ?? $cmd ?? 'update']; } elseif ($method === 'GET') { $parents = $route->parseGetModels(); $metadata = $request->getParameter('metadata'); $addConstraints = function ($alias, $permission, $self) use($route, $event, $user_id) { if ($canIgnore = $permission === SpecialPermission::SAME_USER_OR_IGNORE) { $permission = Permission::SAME_USER; } $hasAccess = $this->canAccessModel($permission, $event->isLoggedInUser()); if (!$hasAccess && $canIgnore) { $route->addConstraint($alias, function (Builder $builder) { return $builder->whereRaw('1 = 0'); }); } elseif ($hasAccess) { $colValue = null; if ($permission === Permission::SAME_USER) { $route->addConstraint($alias, ['user_id', '=', $user_id]); } if ($matchInfo = $self['matchInfo'] ?? null) { if (!empty($matchInfo['name'])) { list($name, $col, $type) = [$matchInfo['name'], $matchInfo['col'], $matchInfo['type']]; $colValue = $type === 'url_param' ? $route->getDefault($name) : ($type === 'var' ? ${$name} : ($type === 'string' ? $matchInfo['value'] : null)); } } if (!empty($col) && isset($colValue)) { $route->addConstraint($alias, [$col, '=', $colValue]); } elseif ($route->getDefault($alias) !== '*' && $permission !== Permission::SAME_USER) { //we block all queries when there is no matchInfo (unless alias explicitly defaults to *) $route->addConstraint($alias, function (Builder $builder) { return $builder->whereRaw('1 = 0'); }); } } else { throw new ModelError(sprintf("Read access denied to %s in %s. Enable auth or change read permission", $alias, $route->getPath())); } }; $joiner = function ($parents) use(&$joiner, $route, $addConstraints) { foreach ($parents['children'] as $alias => $value) { $permission = $route->getJoinPermission($alias); if ($permission !== Permission::EVERYONE) { $addConstraints($alias, $permission, $value['self']); } if (!empty($value['children'])) { $joiner($value); } } }; foreach ($parents as $key => $value) { $self = $value['self']; if ($parentModelClass = $this->resolver->getModel($self['name'])) { $parentAlias = $self['alias']; $permission = $route->getReadPermission($parentAlias); $addConstraints($parentAlias, $permission, $self); } else { throw new RouteError(sprintf("Cannot create model for class: %s in %s", $self['name'], $route->getPath())); } $joiner($value); } $controller = $route->getDefault('controller'); $modifyNodeByAlias = function (&$root, $alias, $modifiers, $append) use(&$modifyNodeByAlias) { foreach ($root as $parent => $child) { if ($parent === $alias) { foreach ($modifiers as $key => $value) { if ($append) { //conditions $root[$parent]['self'][$key] = array_merge($root[$parent]['self'][$key] ?? [], $value); } else { //offset, limit, etc $root[$parent]['self'][$key] = $value; } } return true; } elseif (!empty($child['children']) && ($found = $modifyNodeByAlias($root[$parent]['children'], $alias, $modifiers, $append))) { return $found; } } return false; }; foreach ($route->getAllConstraints() as $alias => $constraint) { if (!$modifyNodeByAlias($parents, $alias, ['conditions' => $constraint], true)) { throw new ModelError("Could not find model {$alias} to add condition"); } } if ($controller === null) { $controller = 'Generic/Page.php'; } if ($request->isAjaxRequest() && !empty($metadata)) { //we override controller for ajax request which have $_GET['metadata'] set $metadata = json_decode($metadata, true); $contentType = 'ajax'; foreach ($metadata ?? [] as $alias => $values) { if (!$modifyNodeByAlias($parents, $alias, $values, false)) { throw new ModelError("Could not find model {$alias} to apply metadata"); } } } $controllerArgs = ['_parents' => $parents]; } $defaults = array_diff_key($route->getDefaults(), array_flip(['url', 'controller', 'auth', 'models', '_route'])); $params = array_merge($defaults, $request->getParameters()); $event = new ControllerEvent($controller, array_merge(['_route' => $route, '_method' => $method, '_params' => $params, '_contentType' => $contentType], $params, $controllerArgs ?? [])); $this->dispatcher->fire(ControllerEvent::CONTROLLER_EXECUTE, $event); $response = $event->getResponse(); } else { throw new AuthError('Forbidden'); } $requestEvent->setResponse($response); }
/** * @return \Minute\Model\ModelEx */ protected function getConfigModel() { return $this->database->isConnected() ? $this->resolver->getModel('Config', true) : null; }