<?php
/**
* This is the primary static class that dispatches all of the commands based on either
* CLI input or invocation from another script, i.e. a controller
*
* @package SilverSmith
* @author Aaron Carlino <hide@address.com>
*/
class SilverSmith {
/**
* @var boolean Determines whether SilverSmith is in CLI mode. Future proofing for a backend GUI.
*/
protected static $cli = false;
/**
* @var string Absolute path to the working directory, e.g. /Applications/MAMP/htdocs/mywebsite
*/
protected static $project_dir = null;
/**
* @var array A cached index of the field configuration YAML files in the lib/ dir
*/
protected static $field_manifest = array ();
/**
* @var array A cached index of the nodes from the SilverSmith project configuration YAML
*/
protected static $node_manifest = array ();
/**
* @var array A cached index of the interface configuration YAML files in the /lib/ dir
*/
protected static $interface_manifest = array ();
/**
* @var string An absolute path to the script that is running SilverSmith, e.g. /usr/local/lib/silversmith/
*/
protected static $script_dir = null;
/**
* @var string An absolute path to the SilverSmith project configuration file, e.g. _project.yml
*/
protected static $yaml_path = null;
/**
* @var string An absolute path to the Git binary
*/
protected static $git_path = null;
/**
* Gets all of the subclasses for a given parent
*
* @todo Is this functionality handled by ClassInfo?
* @param string The parent class name
* @return array
*/
protected static function get_subclasses($parentClassName) {
$classes = array();
foreach (get_declared_classes() as $className) {
if (is_subclass_of($className, $parentClassName))
$classes[] = $className;
}
return $classes;
}
/**
* Execute a Git command at the shell
*
* @todo This is for upgrading mostly. Composer will be a better solution for this
* @param string The Git command to run, less the "git"
* @return string
*/
protected static function git($command) {
return exec(self::$git_path . " $command");
}
/**
* Determines if an upgrade is available using Git commands
*
* @todo Migrate this to Composer
* @return boolean
*/
public static function is_upgrade_available() {
if(!self::$git_path) {
say("Git is not availble. Cannot upgrade.");
return false;
}
$old_dir = getcwd();
chdir(self::$script_dir);
ob_start();
self::git("fetch");
ob_end_clean();
ob_start();
self::git("diff master origin/master");
$response = ob_get_contents();
ob_end_clean();
chdir($old_dir);
if(!empty($response)) {
return true;
}
return false;
}
/**
* Loads all of the field configuration YAML files in the /lib/ directory
*
* @return void
*/
public static function load_field_manifest() {
foreach (glob(self::$script_dir . '/code/lib/fields/*.yml') as $file) {
$yml = new BedrockYAML($file);
self::$field_manifest[basename($file, ".yml")] = $yml;
if ($yml->getAliases()) {
foreach ($yml->getAliases() as $alias) {
self::$field_manifest[(string) $alias] = $yml;
}
}
}
foreach (scandir(self::$script_dir . '/plugins') as $dir) {
if ($dir == "." || $dir == "..")
continue;
if (is_dir($dir)) {
foreach (glob(self::$script_dir . "/plugins/$dir/*.yml") as $file) {
$yml = new BedrockYAML($file);
if ($yml->getPluginType() == "field") {
self::$field_manifest[basename($file, ".yml")] = $yml;
if ($yml->getAliases()) {
foreach ($yml->getAliases() as $alias) {
self::$field_manifest[$alias] = $yml;
}
}
}
}
}
}
}
/**
* Loads all of the configurations for the classes defined in the project definition file
* and stores them in a cached array
*
* @return void
*/
public static function load_class_manifest() {
foreach (SilverSmithProject::get_all_nodes() as $config) {
self::$node_manifest[$config->getKey()] = $config;
}
}
/**
* Lods all of the interface configurations from the /lib/ directory into a cached array
*
* @return void
*/
public static function load_interface_manifest()
{
foreach (glob(self::$script_dir . '/code/lib/interfaces/*.yml') as $file) {
$yml = new BedrockYAML($file);
self::$interface_manifest[basename($file, ".yml")] = $yml;
}
foreach (scandir(self::$script_dir . '/plugins') as $dir) {
if ($dir == "." || $dir == "..")
continue;
if (is_dir($dir)) {
foreach (glob(self::$script_dir . "/plugins/$dir/*.yml") as $file) {
$yml = new BedrockYAML($file);
if ($yml->getPluginType() == "interface") {
self::$interface_manifest[basename($file, ".yml")] = $yml;
}
}
}
}
}
/**
* Switches to the project root, to make sure we're not somewhere in a subdirectory
* e.g. /mysite/code
*
* @return boolean
*/
public static function switch_to_project_root() {
while (dirname(getcwd()) != "/") {
foreach (scandir(".") as $file) {
if ($file == ".." || $file == ".")
continue;
if (is_dir($file)) {
if (file_exists($file . "/cli-script.php")) {
return true;
}
}
}
chdir(dirname(getcwd()));
}
return false;
}
/**
* Rebuild the class maniest e.g. ?flush=all programatically
*
* @todo Can't figure out how to do this in SS3
*/
public static function rebuild_manifest() {
// exec("rm -" . TEMP_FOLDER);
// exec("mkdir " . TEMP_FOLDER);
}
/**
* Rebuilds the database programmatically and keeps track of new tables and fields
*
* @return array
*/
public static function rebuild_database() {
SS_ClassLoader::instance()->getManifest()->regenerate();
ob_start();
$da = DatabaseAdmin::create();
$da->handleRequest(new SS_HTTPRequest("POST","build",array('flush' => 'all')), DataModel::inst());
$output = ob_get_contents();
ob_end_clean();
$database_result = array ();
foreach(explode("\n",$output) as $line) {
if(preg_match('/Table ([A-Za-z0-9_:]+) created/', $line, $matches)) {
$table_name = str_replace(":","",$matches[1]);
if(substr($table_name, -9) == "_versions" || substr($table_name, -5) == "_Live") {
continue;
}
$database_result[$table_name] = array(
'created' => true,
'fields' => array()
);
}
elseif(preg_match('/Field (.*) created as ([A-Za-z0-9_\(\)\"\',]+)/', $line, $matches)) {
list($table_name, $field_name) = explode(".", $matches[1]);
if(substr($table_name, -9) == "_versions" || substr($table_name, -5) == "_Live") {
continue;
}
$field_name = str_replace(":","", $field_name);
$field_type = $matches[2];
if(!isset($database_result[$table_name])) {
$database_result[$table_name] = array (
'created' => false,
'fields' => array()
);
}
$database_result[$table_name]['fields'][] = "$field_name ({$field_type})";
}
}
return $database_result;
}
/**
* A wildcard method for all static function calls. Allows automatic getters and setters
*
* @param string The method name
* @param array The arguments
*/
public static function __callStatic($method, $args) {
$prefix = substr($method, 0, 4);
$suffix = substr($method, 4);
if($prefix == "set_") {
return self::$$suffix = $args[0];
}
elseif($prefix == "get_") {
return self::$$suffix;
}
return call_user_func("SilverSmith::{$m}");
}
/**
* Creates and updates the PHP code for all classes defined in the SilverSmith project configuration file
*
* @see "silversmith help"
* @param The parameters, e.g. from the command line
*/
public static function build_code($params = array ()) {
state("Validating project definition...");
$validator = new SilverSmithSpec_Validator(self::$yaml_path);
$errors = $validator->getErrors();
if (!empty($errors)) {
say(error("Validation error!"));
say("Please fix the following errors:");
foreach ($errors as $e) {
say(" -- " . $e);
}
say("Execution terminated by validation errors.");
die();
} else {
state("OK\n");
}
$page_types = 0;
$page_types_created = 0;
$page_types_updated = 0;
$components = 0;
$components_created = 0;
$components_updated = 0;
$decorators = array();
line();
say(cell("Status", 11, true, "grey", "on_white") . cell("File", 30, true, "grey", "on_white") . cell("Result", 50, true, "grey", "on_white"));
foreach (SilverSmithProject::get_all_nodes() as $node) {
if (!$node->isNew() && !$node->isSilverSmithed()) {
say(cell("Omitted", 11, true, "white", "on_red") . cell("{$node->getKey()}.php", 30) . cell("Class has no code delimiters", 50));
continue;
}
$class = $node->getKey();
if ($node->getDecorator()) {
$decorators[] = $node->getKey();
$class .= "Decorator";
}
$type = $node->getContentType();
if ($type == "PageType")
$page_types++;
elseif ($type == "Component")
$components++;
$new = $node->isNew();
if (!$new) {
$diff = $node->updateFile();
} else {
$node->createFile();
}
if ($new) {
$new_fields = 0;
$new_components = 0;
if ($node->getFields()) {
$new_fields = $node->getFields()->size();
}
if ($node->getComponents()) {
$new_components = $node->getComponents()->size();
}
say(cell("Created", 11, true, "white", "on_green") . cell("{$class}.php", 30) . cell("$new_fields fields and $new_components components.", 50));
if ($type == "PageType")
$page_types_created++;
elseif ($type == "Component")
$components_created++;
} else {
if (!$diff) {
say(cell("Unchanged", 11, true, "grey", "on_yellow") . cell("{$class}.php", 30) . cell("No modifications", 50));
} else {
say(cell("Updated", 11, true, "white", "on_blue") . cell("{$class}.php", 30) . cell(sprintf("%d change(s), %d insertion(s), %d deletion(s)", $diff['changed'], $diff['added'], $diff['deleted']), 50));
if ($type == "PageType")
$page_types_updated++;
elseif ($type == "Component")
$components_updated++;
}
}
}
if (!empty($decorators)) {
line();
$config = trim(file_get_contents(self::$project_dir."/_config.php"));
if (substr($config, 0, -2) == "?>") {
$config = substr_replace($config, "", 0, -2);
}
$added = 0;
foreach ($decorators as $classname) {
if (!Object::has_extension($classname, $classname . "Decorator")) {
say("Adding decorator to $classname", "green");
$config .= "\nObject::add_extension('$classname','{$classname}Decorator');";
$added++;
}
}
if ($added > 0) {
say("Added $added decorator declaration(s) to the _config.php file.", "green");
}
$fh = fopen(self::$project_dir."/_config.php", "w");
fwrite($fh, $config);
fclose($fh);
}
state("Rebuilding database...");
$db = self::rebuild_database();
say("done.");
$tables_created = 0;
$fields_created = 0;
foreach($db as $table_name => $settings) {
if($settings['created']) {
$tables_created++;
}
$fields_created += sizeof($settings['fields']);
}
if($tables_created > 0) {
say("$tables_created tables created","green","bold");
}
if($fields_created > 0) {
say("$fields_created fields created","green","bold");
}
if(sizeof($db) > 0) {
line();
}
foreach($db as $table_name => $settings) {
if($settings['created']) {
say($table_name, "green","bold");
}
else {
say($table_name);
}
foreach($settings['fields'] as $field) {
say("+ $field");
}
state("\n");
}
line();
state("Done");
say("\n\n");
say("Success! ", "green", "bold");
say("\n\n");
say(cell("", 50) . cell("Created", 10, true, "white", "on_green") . cell("Updated", 10, true, "white", "on_blue") . cell("Total", 10, true, "white", "on_red"));
say(cell("Page types:", 50) . cell($page_types_created, 10) . cell($page_types_updated, 10) . cell($page_types, 10));
}
/**
* Creates any templates that do not exist yet, unless the "force" parameter is specified
*
* @see "silversmith help"
* @param The parameters, e.g. from the command line
*/
public static function build_templates($params = array ()) {
$theme_dir = isset($params['theme']) ? "themes/" . $params['theme'] : false;
$force = isset($params['force']);
$specificTemplates = isset($params['list']) ? explode(',',$params['list']) : false;
if (!$theme_dir) {
if (self::$project_dir == project()) {
if (SSViewer::current_theme()) {
$theme_dir = "themes/" . SSViewer::current_theme();
} else {
$theme_dir = project();
}
} else {
$theme_dir = self::$project_dir;
}
}
say("Using theme directory $theme_dir");
if (!file_exists($theme_dir)) {
fail("The theme directory $theme_dir does not exist");
}
if (!is_dir($theme_dir . "/templates"))
mkdir($theme_dir . "/templates");
$layout_dir = "$theme_dir/templates/Layout";
if (!is_dir($layout_dir))
mkdir($layout_dir);
$source = "Page.ss";
if (isset($params['source'])) {
$source = $params['source'];
}
if (!file_exists("$layout_dir/$source")) {
fail("Source template $layout_dir/$source does not exist.");
}
if (!file_exists(self::$yaml_path)) {
fail("File ".self::$yaml_path." does not exist.");
}
SilverSmithProject::load(self::$yaml_path);
$created = 0;
say(cell("Status", 11, true, "grey", "on_white") . cell("File", 30, true, "grey", "on_white") . cell("Result", 40, true, "grey", "on_white"));
line();
foreach (SilverSmithProject::get_page_types() as $node) {
if ($node->getKey() == "SiteConfig")
continue;
if($specificTemplates && !in_array($node->getKey(),$specificTemplates)) {
continue;
}
if (!file_exists("$layout_dir/{$node->getKey()}.ss") || $force) {
$stock = file_get_contents("$layout_dir/$source");
$fh = fopen("$layout_dir/{$node->getKey()}.ss", "w");
$created++;
$notes = "Copied from $source";
if (isset($params['autofill'])) {
if (!preg_match('/\$Content[^A-Za-z0-9_]/', $stock)) {
say(cell("Skipped", 2, "white", "on_red") . cell($node->getKey() . ".ss", 30) . cell("Varible \$Content is not in the template.", 40));
continue;
}
$notes .= " [Auto-filled]";
$template = new BedrockTemplate(file_get_contents(self::$script_dir . "/code/lib/structures/AutoFill.bedrock"));
$template->bind($node);
$autofill = $template->render();
$filled = preg_replace('/\$Content([^A-Za-z0-9_])/', "\$Content\n\n{$autofill}\n\n$1", $stock);
fwrite($fh, $filled);
} else {
fwrite($fh, $stock);
}
fclose($fh);
say(cell("Created", 10, true, "white", "on_green") . cell($node->getKey() . ".ss", 30) . cell($notes, 40));
} else {
say(cell("Bypassed", 11, true, "grey", "on_yellow") . cell($node->getKey() . ".ss", 30) . cell("File exists. Use --force to override", 40));
}
}
line();
say("$created templates created.");
}
/**
* Adds the sample assets, PDFs and images into the assets directory. Used for content population
*
* @see "silversmith help"
*/
public static function add_sample_assets() {
say("Adding sample assets");
$sample_path = self::$script_dir . "/code/lib/sample-assets";
$folder = Folder::find_or_make("silversmith-samples");
exec("cp {$sample_path}/*.* {$folder->getFullPath()}");
say("Syncing database");
$folder->syncChildren();
say("Done.");
}
/**
* Populates content into existing pages and DataObjects
*
* @see "silversmith help"
* @param The parameters, e.g. from the command line
*/
public static function populate($params = array ()) {
if (!isset($params[2])) {
fail("Usage: silversmith populate <class name>");
}
$className = $params[2];
if (!class_exists($className)) {
fail("Class $className does not exist!");
}
$parentField = (!isset($params['parent-field'])) ? "ParentID" : $params['parent-field'];
$parent = (!isset($params['parent'])) ? false : $params['parent'];
$seedingLevel = (!isset($params['seeding-level'])) ? 3 : $params['seeding-level'];
$verbose = isset($params['verbose']);
$site_tree = is_subclass_of($className, "SiteTree");
$fields = (!isset($params['fields'])) ? array() : explode(',',$params['fields']);
if ($parent) {
if (is_numeric($parent)) {
$parentObj = DataList::create("SiteTree")->byId((int) $parent)->first();
if (!$parentObj) {
fail("Page #{$parent} could not be found.");
}
} else {
$parentObj = SiteTree::get_by_link($parent);
if (!$parentObj) {
$parentObj = DataList::create("SiteTree")->where("Title = '" . trim($parent) . "'")->first();
}
if (!$parentObj) {
fail("Page '$parent' could not be found.");
}
}
}
$sample = Folder::find_or_make("silversmith-samples");
if (!$sample->hasChildren()) {
$answer = ask("This project does not have sample assets installed, which can be useful for content seeding. Do you want to install them now? (y/n)");
if (strtolower(trim($answer)) == "y") {
SilverSmith::add_sample_assets();
}
}
$objects = DataList::create($className);
if($parent) {
$objects->filter(array($parentField, $parentObj->ID));
}
foreach($objects as $o) {
state("Populating $o->ClassName: \"{$o->getTitle()}\"...");
SilverSmithUtil::add_default_content($o, $seedingLevel, $fields);
$o->write();
if($site_tree) {
$o->publish("Stage","Live");
}
state("Done.\n");
if($verbose) {
say("Debug output:");
$fields = array_keys(DataObject::custom_database_fields($className));
foreach(array_merge($p->has_many(), $p->many_many()) as $relation => $class) {
$fields[] = $relation;
if($p->$relation()->exists()) {
$p->$relation = implode(',',$p->$relation()->column('ID'));
}
else {
$p->$relation = "(none)";
}
}
if($site_tree) {
$fields = array_merge(array('Title'), $fields);
}
foreach ($fields as $field) {
say("{$field}: {$p->$field}");
}
}
}
}
/**
* Creates pages into the SiteTree and populates them with content
*
* @see "silversmith help"
* @param The parameters, e.g. from the command line
*/
public static function seed_content($params = array ()) {
if (!isset($params[2])) {
fail("Usage: silversmith seed-content <class name>");
}
$className = $params[2];
if (!class_exists($className)) {
fail("Class $className does not exist!");
}
$parentField = (!isset($params['parent-field'])) ? "ParentID" : $params['parent-field'];
$parent = (!isset($params['parent'])) ? false : $params['parent'];
$count = (!isset($params['count'])) ? 10 : (int) $params['count'];
$seedingLevel = (!isset($params['seeding-level'])) ? 3 : $params['seeding-level'];
$verbose = isset($params['verbose']);
$site_tree = is_subclass_of($className, "SiteTree");
if (!$site_tree && $seedingLevel < 2) {
fail("For non SiteTree objects, a seeding level of at least 2 is required.");
}
if ($parent) {
if (is_numeric($parent)) {
$parentObj = DataList::create("SiteTree")->byId((int) $parent);
if (!$parentObj) {
fail("Page #{$parent} could not be found.");
}
} else {
$parentObj = SiteTree::get_by_link($parent);
if (!$parentObj) {
$parentObj = DataList::create("SiteTree")->where("Title = '" . trim($parent) . "'")->first();
}
if (!$parentObj) {
fail("Page '$parent' could not be found.");
}
}
}
$sample = Folder::find_or_make("silversmith-samples");
if (!$sample->hasChildren()) {
$answer = ask("This project does not have sample assets installed, which can be useful for content seeding. Do you want to install them now? (y/n)");
if (strtolower(trim($answer)) == "y") {
SilverSmith::add_sample_assets();
}
}
for ($i = 0; $i < $count; $i++) {
$p = new $className();
if ($site_tree) {
$p->Title = SilverSmithUtil::get_lipsum_words(rand(2, 5));
$p->Content = SilverSmithUtil::get_default_content($p->obj('Content'), $seedingLevel);
state("New {$className} created...");
$p->Status = "Published";
}
if ($parent) {
$p->$parentField = $parentObj->ID;
}
state("Seeding...");
$p->write();
state("Adding content...");
SilverSmithUtil::add_default_content($p, $seedingLevel);
$p->write();
if ($site_tree) {
$p->publish("Stage", "Live");
}
state("Done.\n");
if ($verbose) {
say("Debug output:");
$fields = array_keys(DataObject::custom_database_fields($className));
foreach(array_merge($p->has_many(), $p->many_many()) as $relation => $class) {
$fields[] = $relation;
if($p->$relation()->exists()) {
$p->$relation = implode(',',$p->$relation()->column('ID'));
}
else {
$p->$relation = "(none)";
}
}
if($site_tree) {
$fields = array_merge(array('Title'), $fields);
}
foreach ($fields as $field) {
say("{$field}: {$p->$field}");
}
}
}
}
/**
* Builds out the SiteTree hierarchy as specified in _fixtures.txt
*
* @see "silversmith help"
* @param The parameters, e.g. from the command line
*/
public static function build_fixtures($params = array ()) {
ClassInfo::reset_db_cache();
$fixtures_file = isset($params['file']) ? $params['file'] : self::$project_dir."/_fixtures.txt";
if (!file_exists($fixtures_file)) {
fail("The file $fixtures_file doesn't exist.");
}
$code = file_get_contents($fixtures_file);
$architectureData = array();
$lines = explode("\n", $code);
if (empty($lines)) {
fail("The files $fixtures_file is empty.");
}
$sample = Folder::find_or_make("silversmith-samples");
if (!$sample->hasChildren()) {
$answer = ask("This project does not have sample assets installed, which can be useful for content seeding. Do you want to install them now? (y/n)");
if (strtolower(trim($answer)) == "y") {
SilverSmith::add_sample_assets();
}
}
$answer = ask("This process will completely empty and repopulate your site tree. Are you sure you want to continue? (y/n)");
if (strtolower($answer) != "y")
die();
say("Parsing architecture file...");
foreach ($lines as $line) {
if (empty($line))
continue;
$level = 0;
$count = 1;
$class = "Page";
$title = $line;
preg_match('/^[ ]+[^ ]/', $line, $matches);
if ($matches) {
$level = strlen(substr(reset($matches), 0, -1));
}
if (stristr($line, ">")) {
list($title, $class) = explode(" > ", $line);
$class = SilverSmithUtil::proper_form($class);
}
preg_match('/\*[0-9]+/', $title, $m);
if ($m) {
$match = reset($m);
$count = (int) trim(str_replace("*", "", $match));
$title = str_replace($match, "", $title);
}
$architectureData[] = array(
'title' => trim($title),
'level' => $level,
'class' => trim($class),
'count' => $count,
'new' => !class_exists($class) || !in_array($class, ClassInfo::getValidSubclasses("SiteTree"))
);
}
// Clean the slate
say("Deleting current site tree");
DB::query("DELETE FROM SiteTree");
DB::query("DELETE FROM SiteTree_Live");
DB::query("DELETE FROM SiteTree_versions");
say("Done.");
// Update the DB with any new page types
$new = array();
say("Checking architecture file for new page types...");
foreach ($architectureData as $arr) {
if ($arr['new']) {
$new[] = $arr['class'];
SilverSmithProject::get_configuration()->addNode($arr['class'], "PageTypes");
SilverSmithProject::get_node($arr['class'])->createFile();
say(success("Created " . $arr['class']));
}
}
if (!empty($new)) {
state("Rebuilding database to support " . sizeof($new) . " new page types...");
$result = self::rebuild_database();
self::rebuild_manifest();
state("Done\n");
}
$previousParentIDs = array(
'0' => '0'
);
$previousLevel = 0;
$seeding = isset($params['seeding-level']) ? $params['seeding-level'] : 1;
$total = 0;
foreach ($architectureData as $arr) {
$parentID = 0;
$currentLevel = $arr['level'];
$title = $arr['title'];
$class = $arr['class'];
$count = $arr['count'];
$indent = "";
while (strlen($indent) < $currentLevel * 2)
$indent .= " ";
if ($currentLevel > 0) {
$parentID = $previousParentIDs[$currentLevel - 2];
}
for ($i = 0; $i < $count; $i++) {
$p = new $class();
if (strtolower($title) == "_auto_") {
$p->Title = SilverSmithUtil::get_lipsum_words(rand(2, 5));
} else {
$p->Title = $title;
}
state($indent . $p->Title, "green", "bold");
state(" [{$class}] created...");
if ($seeding > 0) {
state("Seeding...");
$p->Content = SilverSmithUtil::get_default_content($p->obj('Content'), $seeding);
}
$p->Status = "Published";
$p->ParentID = $parentID;
$p->write();
if ($seeding > 1) {
SilverSmithUtil::add_default_content($p, $seeding);
}
$p->write();
$p->publish("Stage", "Live");
state("Done.\n");
$total++;
$previousParentIDs[$currentLevel] = $p->ID;
}
}
$errorPages = DataList::create("ErrorPage");
if ($errorPages->count()) {
state("Fixing error pages...");
$max = DB::query("SELECT MAX(Sort) FROM SiteTree")->value();
foreach ($errorPages as $e) {
$max++;
$e->Sort = $max;
$e->write();
$e->publish("Stage", "Live");
}
("Done\n");
}
self::rebuild_manifest();
say(success("Success!"));
state("Important", "red");
state(": You must ");
state("restart your browser", null, null, "bold");
state(" to clear your session in order to view the architecture changes.\n");
}
/**
* Uninstalls the CLI tool from the filesystem
*
* @see "silversmith help"
* @param The parameters, e.g. from the command line
*/
public static function cli_uninstall($params = array ()) {
$response = (isset($params['force'])) ? "y" : ask("Are you sure you want to uninstall the SilverSmith CLI tools? (y/n)");
if (strtolower($response) == "y") {
exec("sudo rm -rf /usr/local/lib/silversmith");
exec("sudo rm /usr/local/bin/silversmith");
}
}
/**
* Initializes a new SilverSmith project
*
* @see "silversmith help"
* @param The parameters, e.g. from the command line
*/
public static function init($params = array ()) {
$no_assets = isset($params['no-assets']);
if (!file_exists(self::$project_dir . "/_project.yml")) {
if (isset($params['example'])) {
$contents = file_get_contents(self::$script_dir . "/code/lib/_project.yml");
} else {
$contents = "PageTypes: {}\n\nComponents: {}\n";
}
$fh = fopen(self::$project_dir . "/_project.yml", "w");
fwrite($fh, $contents);
fclose($fh);
say(success("Created _project.yml"));
} else {
say("File _project.yml already exists.");
}
if (!file_exists(self::$project_dir . "/_fixtures.txt")) {
$fh = fopen(self::$project_dir . "/_fixtures.txt", "w");
if (isset($params['example'])) {
fwrite($fh, file_get_contents(self::$script_dir . "/code/lib/_fixtures.txt"));
}
fclose($fh);
say(success("Created _fixtures.txt"));
} else {
say("File _fixtures.txt already exists.");
}
if (!$no_assets) {
SilverSmith::add_sample_assets();
}
}
/**
* Initializes a new module in the current project directory
*
* @see "silversmith help"
* @param The parameters, e.g. from the command line
*/
public static function init_module($params = array ()) {
if (!isset($params[2]) || empty($params[2])) {
fail("Please specify a module name.");
}
$d = SilverSmithUtil::proper_form($params[2]);
if (is_dir($d)) {
fail("Module $d already exists.");
}
mkdir($d);
say($d);
mkdir("$d/code");
say("$d/code");
mkdir("$d/css");
say("$d/css");
mkdir("$d/javascript");
say("$d/javascript");
mkdir("$d/templates");
say("$d/templates");
mkdir("$d/templates/Layout");
say("$d/templates/Layout");
mkdir("$d/templates/Includes");
say("$d/templates/Includes");
$fh = fopen("$d/_config.php", "w");
fwrite($fh, "<?php\n");
fclose($fh);
say("$d/_config.php");
}
/**
* Upgrades this version of SilverSmith
*
* @todo Move this to Composer
* @see "silversmith help"
* @param The parameters, e.g. from the command line
*/
public static function upgrade($params = array ()) {
if (self::is_upgrade_available()) {
$response = ask("An upgrade is available. Install now? (y/n)");
if (strtolower($response) == "y") {
$old_dir = getcwd();
chdir(self::$script_dir);
self::git("reset --hard");
self::git("pull");
$fh = fopen(self::$script_dir."/upgrade","w");
fwrite($fh, time());
fclose($fh);
chdir($old_dir);
}
return;
} else {
say("SilverSmith is up to date.");
}
}
/**
* Displays the required spec for the SilverSmith project configuration file
*
* @see "silversmith help"
* @param The parameters, e.g. from the command line
*/
public static function spec($params = array ()) {
self::load_interface_manifest();
say("PageTypes:");
say(" YourPageType:");
say(" Fields:");
say(" Tagline:");
say("");
$required = SilverSmithSpec::get("Field.RequiredNodes")->toArray();
foreach (SilverSmithSpec::get("Field.BaseNodes") as $key => $node) {
$r = in_array($key, $required) ? ", required" : "";
$v = "";
if ($vals = $node->getPossibleValues()) {
$v = " (" . implode(',', $vals) . ")";
}
say(" ## {$node->getDescription()} [{$node->getDataType()}{$r}{$v}]");
say(" {$key}: {$node->getExample()}");
say("");
}
say(" ## Components related to and managed in the context of this page");
say(" Components:");
say(" Testimonial:");
say("");
$required = SilverSmithSpec::get("Component.RequiredNodes");
if($required) $required = $required->toArray();
foreach (SilverSmithSpec::get("Component.AvailableNodes") as $key => $node) {
if (in_array($key, array(
'Fields',
'Components',
'Interface'
)))
continue;
$r = in_array($key, $required) ? ", required" : "";
$v = "";
if ($vals = $node->getPossibleValues()) {
$v = " (" . implode(',', $vals->toArray()) . ")";
}
say(" ## {$node->getDescription()} [{$node->getDataType()}{$r}{$v}]");
say(" {$key}: {$node->getExample()}");
say("");
}
say(" ## Define the interface used to manage this component in the context of the page");
say(" Interface:");
say("");
$required = SilverSmithSpec::get("Interface.RequiredNodes");
if($required) $required = $required->toArray();
foreach (SilverSmithSpec::get("Interface.BaseNodes") as $key => $node) {
$r = in_array($key, $required) ? ", required" : "";
$v = "";
if ($vals = $node->getPossibleValues()) {
$v = " (" . implode(',', $vals->toArray()) . ")";
}
say(" ## {$node->getDescription()} [{$node->getDataType()}{$r}{$v}]");
say(" {$key}: {$node->getExample()}");
say("");
}
say(" ## Component fields can be defined in the same block as its parent page");
say(" Fields:");
say(" Author:");
say("");
$required = SilverSmithSpec::get("Field.RequiredNodes");
if($required) $required = $required->toArray();
foreach (SilverSmithSpec::get("Field.BaseNodes") as $key => $node) {
if (in_array($key, array(
'Tab',
'Before'
)))
continue;
$r = in_array($key, $required) ? ", required" : "";
$v = "";
if ($vals = $node->getPossibleValues()) {
$v = " (" . implode(',', $vals->toArray()) . ")";
}
say(" ## {$node->getDescription()} [{$node->getDataType()}{$r}{$v}]");
say(" {$key}: {$node->getExample()}");
say("");
}
$required = SilverSmithSpec::get("PageType.RequiredNodes");
if($required) $required = $required->toArray();
foreach (SilverSmithSpec::get("PageType.AvailableNodes") as $key => $node) {
if (in_array($key, array(
'Fields',
'Components'
)))
continue;
$r = in_array($key, $required) ? ", required" : "";
$v = "";
if ($vals = $node->getPossibleValues()) {
$v = " (" . implode(',', $vals->toArray()) . ")";
}
say(" ## {$node->getDescription()} [{$node->getDataType()}{$r}{$v}]");
say(" {$key}: {$node->getExample()}");
say("");
}
say(" ## Standalone components that are not necessarily related to or managed on a specific page type.");
say("Components:");
say(" Client:");
say("");
$required = SilverSmithSpec::get("Component.RequiredNodes");
if($required) $required = $required->toArray();
foreach (SilverSmithSpec::get("Component.AvailableNodes") as $key => $node) {
if (in_array($key, array(
'Fields',
'Components',
'Interface',
'Type',
'Tab'
)))
continue;
$r = in_array($key, $required) ? ", required" : "";
$v = "";
if ($vals = $node->getPossibleValues()) {
$v = " (" . implode(',', $vals->toArray()) . ")";
}
say(" ## {$node->getDescription()} [{$node->getDataType()}{$r}{$v}]");
say(" {$key}: {$node->getExample()}");
say("");
}
}
public static function fix_mamp($params = array ()) {
if(!is_dir("/var/mysql")) {
exec("sudo mkdir /var/mysql;ln -s /Applications/MAMP//tmp/mysql/mysql.sock /var/mysql/mysql.sock");
}
else {
exec("sudo ln -s /Applications/MAMP//tmp/mysql/mysql.sock /var/mysql/mysql.sock");
}
say("Fixed!");
}
public static function help($params = array ()) {
$cli = new BedrockYAML(self::$script_dir."/code/lib/_cli.yml");
$allowed_actions = $cli->getAllowedActions();
say(cell("Command", 20, true, "grey", "on_white") . cell("Description", 50, true, "grey", "on_white") . cell("Options", 50, true, "grey", "on_white"));
foreach ($allowed_actions as $a) {
$options = array();
foreach ($a->getOptions() as $o) {
$options[] = "[" . $o->get('arg') . "] ";
foreach ($o->get('description') as $line) {
$options[] = $line;
}
$options[] = "";
}
array_pop($options);
$descriptions = $a->getDescription()->toArray();
$source = (sizeof($options) > sizeof($descriptions)) ? $options : $descriptions;
for ($i = 0; $i < sizeof($source); $i++) {
$cmd = ($i == 0) ? $a->getKey() : "";
state(cell($cmd, 20, false));
$desc = isset($descriptions[$i]) ? $descriptions[$i] : "";
$opt = isset($options[$i]) ? $options[$i] : "";
state(cell($desc, 50, false));
state(cell($opt, 50, false));
state("\n");
}
say(cell("", 20) . cell("", 50) . cell("", 50));
}
}
}
/**
* Helper functions
*-----------------------------------------------*/
function say($text, $foreground = null, $background = null, $style = null) {
SilverSmithPrompt::say($text, $background, $foreground, $style);
}
function state($text, $foreground = null, $background = null, $style = null) {
SilverSmithPrompt::write($text, $background, $foreground, $style);
}
function ask($msg) {
say($msg);
return trim(fgets(STDIN));
}
function fail($text, $foreground = "white", $background = "on_red", $style = null) {
SilverSmithPrompt::say($text, $background, $foreground, $style);
die();
}
function line() {
say("----------------------------------------------------------------------------------------------");
}
function success($msg) {
state(" $msg ", "white", "on_green");
}
function warn($msg) {
state(" $msg ", "grey", "on_yellow");
}
function info($msg) {
state(" $msg ", "white", "on_blue");
}
function error($msg) {
state(" $msg ", "white", "on_red");
}
/**
* Creates a text-based table cell for output to the CLI
*
* @param string The text for the table cell
* @param integer The width, in characters of the table cell
* @param boolean Underline the table cell, e.g. a table heading
* @param string The foreground color
* @param string The background color
* @return string
*/
function cell($text, $width = 30, $underline = true, $foreground = null, $background = null) {
ob_start();
if ($width < strlen($text)) {
$text = substr($text, 0, $width - 3);
}
$cell = " {$text}";
while (strlen($cell) < $width)
$cell .= " ";
$u = $underline ? "underline" : null;
state($cell, $foreground, $background, $u);
state("|");
$ret = ob_get_contents();
ob_end_clean();
return $ret;
}