['required', 'regex:' . self::UNQUALIFIED_CLASS_NAME_PATTERN, 'uniqModelName'], 'databaseTable' => ['required'], 'addTimestamps' => ['timestampColumnsMustExist'], 'addSoftDeleting' => ['deletedAtColumnMustExist'] ]; public static function listPluginModels($pluginCodeObj) { $modelsDirectoryPath = $pluginCodeObj->toPluginDirectoryPath().'/models'; $pluginNamespace = $pluginCodeObj->toPluginNamespace(); $modelsDirectoryPath = File::symbolizePath($modelsDirectoryPath); if (!File::isDirectory($modelsDirectoryPath)) { return []; } $parser = new ModelFileParser(); $result = []; foreach (new DirectoryIterator($modelsDirectoryPath) as $fileInfo) { if (!$fileInfo->isFile()) { continue; } if ($fileInfo->getExtension() != 'php') { continue; } $filePath = $fileInfo->getPathname(); $contents = File::get($filePath); $modelInfo = $parser->extractModelInfoFromSource($contents); if (!$modelInfo) { continue; } if (!Str::startsWith($modelInfo['namespace'], $pluginNamespace.'\\')) { continue; } $model = new ModelModel(); $model->className = $modelInfo['class']; $model->databaseTable = isset($modelInfo['table']) ? $modelInfo['table'] : null; $result[] = $model; } return $result; } public function save() { $this->validate(); $modelFilePath = $this->getFilePath(); $namespace = $this->getPluginCodeObj()->toPluginNamespace().'\\Models'; $structure = [ $modelFilePath => 'model.php.tpl' ]; $variables = [ 'namespace' => $namespace, 'classname' => $this->className, 'table' => $this->databaseTable ]; $dynamicContents = []; $generator = new FilesystemGenerator('$', $structure, '$/rainlab/builder/classes/modelmodel/templates'); $generator->setVariables($variables); if ($this->addSoftDeleting) { $dynamicContents[] = $generator->getTemplateContents('soft-delete.php.tpl'); } if (!$this->addTimestamps) { $dynamicContents[] = $generator->getTemplateContents('no-timestamps.php.tpl'); } $generator->setVariable('dynamicContents', implode('', $dynamicContents)); $generator->generate(); } public function validate() { $path = File::symbolizePath('$/'.$this->getFilePath()); $this->validationMessages = [ 'className.uniq_model_name' => Lang::get('rainlab.builder::lang.model.error_class_name_exists', ['path'=>$path]), 'addTimestamps.timestamp_columns_must_exist' => Lang::get('rainlab.builder::lang.model.error_timestamp_columns_must_exist'), 'addSoftDeleting.deleted_at_column_must_exist' => Lang::get('rainlab.builder::lang.model.error_deleted_at_column_must_exist') ]; Validator::extend('uniqModelName', function($attribute, $value, $parameters) use ($path) { $value = trim($value); if (!$this->isNewModel()) { // Editing models is not supported at the moment, // so no validation is required. return true; } return !File::isFile($path); }); $columns = $this->isNewModel() ? Schema::getColumnListing($this->databaseTable) : []; Validator::extend('timestampColumnsMustExist', function($attribute, $value, $parameters) use ($columns) { return $this->validateColumnsExist($value, $columns, ['created_at', 'updated_at']); }); Validator::extend('deletedAtColumnMustExist', function($attribute, $value, $parameters) use ($columns) { return $this->validateColumnsExist($value, $columns, ['deleted_at']); }); parent::validate(); } public function getDatabaseTableOptions() { $pluginCode = $this->getPluginCodeObj()->toCode(); $tables = DatabaseTableModel::listPluginTables($pluginCode); return array_combine($tables, $tables); } private static function getTableNameFromModelClass($pluginCodeObj, $modelClassName) { if (!self::validateModelClassName($modelClassName)) { throw new SystemException('Invalid model class name: '.$modelClassName); } $modelsDirectoryPath = File::symbolizePath($pluginCodeObj->toPluginDirectoryPath().'/models'); if (!File::isDirectory($modelsDirectoryPath)) { return ''; } $modelFilePath = $modelsDirectoryPath.'/'.$modelClassName.'.php'; if (!File::isFile($modelFilePath)) { return ''; } $parser = new ModelFileParser(); $modelInfo = $parser->extractModelInfoFromSource(File::get($modelFilePath)); if (!$modelInfo || !isset($modelInfo['table'])) { return ''; } return $modelInfo['table']; } public static function getModelFields($pluginCodeObj, $modelClassName) { $tableName = self::getTableNameFromModelClass($pluginCodeObj, $modelClassName); // Currently we return only table columns, // but eventually we might want to return relations as well. return Schema::getColumnListing($tableName); } public static function getModelColumnsAndTypes($pluginCodeObj, $modelClassName) { $tableName = self::getTableNameFromModelClass($pluginCodeObj, $modelClassName); if (!DatabaseTableModel::tableExists($tableName)) { throw new ApplicationException('Database table not found: '.$tableName); } $schema = DatabaseTableModel::getSchema(); $tableInfo = $schema->getTable($tableName); $columns = $tableInfo->getColumns(); $result = []; foreach ($columns as $column) { $columnName = $column->getName(); $typeName = $column->getType()->getName(); if ($typeName == EnumDbType::TYPENAME) { continue; } $item = [ 'name' => $columnName, 'type' => MigrationColumnType::toMigrationMethodName($typeName, $columnName) ]; $result[] = $item; } return $result; } public static function getPluginRegistryData($pluginCode, $subtype) { $pluginCodeObj = new PluginCode($pluginCode); $models = self::listPluginModels($pluginCodeObj); $result = []; foreach ($models as $model) { $fullClassName = $pluginCodeObj->toPluginNamespace().'\\Models\\'.$model->className; $result[$fullClassName] = $model->className; } return $result; } public static function getPluginRegistryDataColumns($pluginCode, $modelClassName) { $classParts = explode('\\', $modelClassName); if (!$classParts) { return []; } $modelClassName = array_pop($classParts); if (!self::validateModelClassName($modelClassName)) { return []; } $pluginCodeObj = new PluginCode($pluginCode); $columnNames = self::getModelFields($pluginCodeObj, $modelClassName); $result = []; foreach ($columnNames as $columnName) { $result[$columnName] = $columnName; } return $result; } public static function validateModelClassName($modelClassName) { return class_exists($modelClassName) || !!preg_match(self::UNQUALIFIED_CLASS_NAME_PATTERN, $modelClassName); } protected function getFilePath() { return $this->getPluginCodeObj()->toFilesystemPath().'/models/'.$this->className.'.php'; } protected function validateColumnsExist($value, $columns, $columnsToCheck) { if (!strlen(trim($this->databaseTable))) { return true; } if (!$this->isNewModel()) { // Editing models is not supported at the moment, // so no validation is required. return true; } if (!$value) { return true; } return count(array_intersect($columnsToCheck, $columns)) == count($columnsToCheck); } }