2018-11-05 14:35:41 -06:00
< ? php namespace System\Console ;
use App ;
2018-12-16 09:43:33 -06:00
use Event ;
2018-11-05 14:35:41 -06:00
use Exception ;
use Cms\Classes\Theme ;
2018-12-16 09:43:33 -06:00
2018-11-05 14:35:41 -06:00
use Illuminate\Console\Command ;
use Symfony\Component\Console\Input\InputOption ;
use Symfony\Component\Console\Input\InputArgument ;
/**
* Console command to sync a theme between the DB and Filesystem layers .
*
* theme : sync name -- paths = file / to / sync . md , other / file / to / sync . md -- target = filesystem -- force
2018-12-16 09:43:33 -06:00
*
2018-11-05 14:35:41 -06:00
* - name defaults to the currently active theme
* - -- paths defaults to all paths within the theme , otherwise comma - separated list of paths relative to the theme directory
2019-06-01 13:42:29 +10:00
* - -- target defaults to " filesystem " , the source will whichever of filesystem vs database is not the target
2018-11-05 14:35:41 -06:00
* - -- force bypasses the confirmation request
*
* @ package october\system
* @ author Luke Towers
*/
class ThemeSync extends Command
{
use \Illuminate\Console\ConfirmableTrait ;
/**
* The console command name .
* @ var string
*/
protected $name = 'theme:sync' ;
/**
* The console command description .
* @ var string
*/
protected $description = 'Sync an existing theme between the DB and Filesystem layers' ;
2018-12-16 09:43:33 -06:00
/**
* @ var \October\Rain\Datasource\DatasourceInterface The theme ' s AutoDatasource instance
*/
protected $datasource ;
/**
* @ var string The datasource key that the sync is targeting
*/
protected $target ;
/**
* @ var string The datasource key that the sync is sourcing from
*/
protected $source ;
2018-11-05 14:35:41 -06:00
/**
* Execute the console command .
* @ return void
*/
public function handle ()
{
// Check to see if the application even uses a database
if ( ! App :: hasDatabase ()) {
return $this -> error ( " The application is not using a database. " );
}
// Check to see if the DB layer is enabled
2018-12-16 09:43:33 -06:00
if ( ! Theme :: databaseLayerEnabled ()) {
2019-06-01 12:40:17 +10:00
return $this -> error ( " cms.databaseTemplates is not enabled, enable it first and try again. " );
2018-11-05 14:35:41 -06:00
}
// Check to see if the provided theme exists
$themeName = $this -> argument ( 'name' ) ? : Theme :: getActiveThemeCode ();
$themeExists = Theme :: exists ( $themeName );
if ( ! $themeExists ) {
$themeName = strtolower ( str_replace ( '.' , '-' , $themeName ));
$themeExists = Theme :: exists ( $themeName );
}
if ( ! $themeExists ) {
return $this -> error ( sprintf ( 'The theme %s does not exist.' , $themeName ));
}
2018-12-16 09:43:33 -06:00
$theme = Theme :: load ( $themeName );
2018-11-05 14:35:41 -06:00
// Get the target and source datasources
$availableSources = [ 'filesystem' , 'database' ];
2019-06-01 13:42:29 +10:00
$target = $this -> option ( 'target' ) ? : 'filesystem' ;
2019-10-09 22:54:13 +08:00
$source = ( $target === 'filesystem' ) ? 'database' : 'filesystem' ;
2018-11-05 14:35:41 -06:00
if ( ! in_array ( $target , $availableSources )) {
2019-06-01 13:42:29 +10:00
return $this -> error ( sprintf ( " Provided --target of %s is invalid. Allowed: filesystem, database " , $target ));
2018-11-05 14:35:41 -06:00
}
2019-10-09 22:54:13 +08:00
2018-12-16 09:43:33 -06:00
$this -> source = $source ;
$this -> target = $target ;
2018-11-05 14:35:41 -06:00
2019-04-19 23:07:48 +08:00
// Get the theme paths, taking into account if the user has specified paths
$userPaths = $this -> option ( 'paths' ) ? : null ;
$themePaths = array_keys ( $theme -> getDatasource () -> getSourcePaths ( $source ));
if ( ! isset ( $userPaths )) {
$paths = $themePaths ;
2019-10-09 22:54:13 +08:00
} else {
2019-04-19 23:07:48 +08:00
$paths = [];
$userPaths = array_map ( 'trim' , explode ( ',' , $userPaths ));
foreach ( $userPaths as $userPath ) {
foreach ( $themePaths as $themePath ) {
$pregMatch = '/' . str_replace ( '/' , '\/' , $userPath ) . '/i' ;
2018-12-16 09:43:33 -06:00
2019-04-19 23:07:48 +08:00
if ( $userPath === $themePath || preg_match ( $pregMatch , $themePath )) {
$paths [] = $themePath ;
2018-12-16 09:43:33 -06:00
}
}
2019-04-19 23:07:48 +08:00
}
}
// Determine valid paths based on the models made available for syncing
$validPaths = [];
/**
* @ event system . console . theme . sync . getAvailableModelClasses
* Defines the Halcyon models to be made available to the `theme:sync` tool .
*
* Example usage :
*
* Event :: listen ( 'system.console.theme.sync.getAvailableModelClasses' , function () {
* return [
* Meta :: class ,
* Page :: class ,
* Layout :: class ,
* Content :: class ,
* Partial :: class ,
* ];
* });
*
*/
2019-06-01 13:42:29 +10:00
$eventResults = Event :: fire ( 'system.console.theme.sync.getAvailableModelClasses' );
2019-04-19 23:07:48 +08:00
$validModels = [];
2019-06-01 13:42:29 +10:00
foreach ( $eventResults as $result ) {
if ( ! is_array ( $result )) {
2019-04-19 23:07:48 +08:00
continue ;
}
2019-06-01 13:42:29 +10:00
foreach ( $result as $modelClass ) {
$modelObj = new $modelClass ;
2018-12-16 09:43:33 -06:00
2019-06-01 13:42:29 +10:00
if ( $modelObj instanceof \October\Rain\Halcyon\Model ) {
$validModels [] = $modelObj ;
2019-04-19 23:07:48 +08:00
}
2018-12-16 09:43:33 -06:00
}
2019-04-19 23:07:48 +08:00
}
// Check each path and map it to a corresponding model
foreach ( $paths as $path ) {
foreach ( $validModels as $model ) {
if (
starts_with ( $path , $model -> getObjectTypeDirName () . '/' )
&& in_array ( pathinfo ( $path , PATHINFO_EXTENSION ), $model -> getAllowedExtensions ())
) {
$validPaths [ $path ] = get_class ( $model );
// Skip to the next path
continue 2 ;
}
}
}
if ( count ( $validPaths ) === 0 ) {
return $this -> error ( sprintf ( 'No applicable paths found for %s.' , $source ));
2018-11-05 14:35:41 -06:00
}
// Confirm with the user
2019-07-18 22:50:37 +08:00
if ( ! $this -> confirmToProceed ( sprintf ( 'This will OVERWRITE the %s provided paths in "themes/%s" on the %s with content from the %s' , count ( $validPaths ), $themeName , $target , $source ), true )) {
2018-11-05 14:35:41 -06:00
return ;
}
try {
2018-12-16 09:43:33 -06:00
$this -> info ( 'Syncing files, please wait...' );
2019-04-19 23:07:48 +08:00
$progress = $this -> output -> createProgressBar ( count ( $validPaths ));
2018-12-16 09:43:33 -06:00
$this -> datasource = $theme -> getDatasource ();
2019-04-19 23:07:48 +08:00
foreach ( $validPaths as $path => $model ) {
$entity = $this -> getModelForPath ( $path , $model , $theme );
if ( ! isset ( $entity )) {
continue ;
}
2018-11-05 14:35:41 -06:00
2019-04-19 23:07:48 +08:00
$this -> datasource -> pushToSource ( $entity , $target );
2018-12-16 09:43:33 -06:00
$progress -> advance ();
}
$progress -> finish ();
$this -> info ( '' );
$this -> info ( sprintf ( 'The theme %s has been successfully synced from the %s to the %s.' , $themeName , $source , $target ));
2018-11-05 14:35:41 -06:00
}
catch ( Exception $ex ) {
$this -> error ( $ex -> getMessage ());
}
}
2018-12-16 09:43:33 -06:00
/**
2019-04-19 23:07:48 +08:00
* Get the correct Halcyon model for the provided path from the source datasource and load the requested path data .
2018-12-16 09:43:33 -06:00
*
* @ param string $path
2019-04-19 23:07:48 +08:00
* @ param string $model
* @ param \Cms\Classes\Theme $theme
* @ return \October\Rain\Halycon\Model
2018-12-16 09:43:33 -06:00
*/
2019-06-01 13:42:29 +10:00
protected function getModelForPath ( $path , $modelClass , $theme )
2018-12-16 09:43:33 -06:00
{
$originalSource = $this -> datasource -> activeDatasourceKey ;
$this -> datasource -> activeDatasourceKey = $this -> source ;
2019-06-01 13:42:29 +10:00
$modelObj = new $modelClass ;
$entity = $modelClass :: load (
$theme ,
str_replace ( $modelObj -> getObjectTypeDirName () . '/' , '' , $path )
);
2019-04-19 23:07:48 +08:00
if ( ! isset ( $entity )) {
return null ;
}
2018-12-16 09:43:33 -06:00
$this -> datasource -> activeDatasourceKey = $originalSource ;
2019-04-19 23:07:48 +08:00
return $entity ;
2018-12-16 09:43:33 -06:00
}
2018-11-05 14:35:41 -06:00
/**
* Get the console command arguments .
* @ return array
*/
protected function getArguments ()
{
return [
[ 'name' , InputArgument :: OPTIONAL , 'The name of the theme (directory name). Defaults to currently active theme.' ],
];
}
/**
* Get the console command options .
* @ return array
*/
protected function getOptions ()
{
return [
2019-04-19 23:07:48 +08:00
[ 'paths' , null , InputOption :: VALUE_REQUIRED , 'Comma-separated specific paths (relative to provided theme directory) to specificaly sync. Default is all paths. You may use regular expressions.' ],
2019-06-01 13:42:29 +10:00
[ 'target' , null , InputOption :: VALUE_REQUIRED , 'The target of the sync, the other will be used as the source. Defaults to "filesystem", can be "database"' ],
2018-11-05 14:35:41 -06:00
[ 'force' , null , InputOption :: VALUE_NONE , 'Force the operation to run.' ],
];
}
}