5 * Please supply a file description.
9 * Implements hook_drush_command().
11 function omega_drush_command() {
12 $items['omega-subtheme'] = array(
13 'description' => dt('Creates a Omega subtheme.'),
15 'name' => dt('The name of your subtheme.'),
18 'destination' => dt('The destination of your subtheme. Defaults to "all" (sites/all/themes).'),
19 'machine-name' => dt('The machine-readable name of your subtheme. This will be auto-generated from the human-readable name if omitted.'),
20 'starterkit' => dt('The starterkit that your subtheme should use. Defaults to "omega_starterkit".'),
21 'basetheme' => dt('Overrides the base theme defined in the starterkit.'),
22 'enable' => dt('Automatically enable the subtheme after creation.'),
23 'set-default' => dt('Automatically enable the subtheme after creation and make it the default theme.'),
26 'drush omega-subtheme "My Theme"' => dt('Creates a Omega subtheme called "My Theme".'),
27 'drush omega-subtheme "My Theme" --destination=example.com' => dt('Creates a Omega subtheme called "My Theme" in sites/example.com/themes.'),
28 'drush omega-subtheme "My Theme" --starterkit=my_custom_starterkit' => dt('Uses a custom starterkit to create a Omega subtheme called "My Theme" in sites/all/themes (default).'),
30 'aliases' => array('osub'),
33 $items['omega-export'] = array(
34 'description' => dt('Exports the theme settings of a given theme from the database to the .info file.'),
36 'theme' => dt('The machine-readable name of the theme to export the theme settings for.'),
39 'revert' => dt('Purges the theme settings from the database after exporting them to the .info file.'),
42 'drush omega-export foo' => dt('Exports the theme settings of the "foo" theme to the "foo.info" file in that theme.'),
43 'drush omega-export foo --revert' => dt('Purges the theme settings of the "foo" theme from the database after exporting them to the .info file.'),
45 'aliases' => array('oexp'),
48 $items['omega-revert'] = array(
49 'description' => dt('Reverts the theme settings of a given theme by deleting them from the database.'),
51 'theme' => dt('The machine-readable name of the theme to revert the theme settings for.'),
54 'drush omega-revert foo' => dt('Reverts the theme settings of the "foo" theme.'),
56 'aliases' => array('orev'),
59 $items['omega-layout'] = array(
60 'description' => dt('Generates a layout from the starterkit.'),
62 'theme' => dt('The machine-readable name of the theme to create the layout for.'),
63 'layout' => dt('The name of your layout.'),
66 'machine-name' => dt('The machine-readable name of the layout. This will be auto-generated from the human-readable name if omitted.'),
69 'drush omega-layout foo bar' => dt('Creates a layout "bar" for the "foo" theme.'),
71 'aliases' => array('olay'),
78 * Implements hook_drush_help().
80 function omega_drush_help($section) {
82 case
'drush:omega-subtheme':
83 return dt('Generates a subtheme.');
84 case
'drush:omega-export':
85 return dt('Exports the theme settings of a given theme.');
86 case
'drush:omega-revert':
87 return dt('Reverts the theme settings of a given theme.');
88 case
'drush:omega-layout':
89 return dt('Generates a layout.');
94 * Implements drush_hook_COMMAND_validate().
96 function drush_omega_subtheme_validate($name = NULL
) {
97 // Rebuild the theme data so that we can safely check for the existance of
98 // themes by using the information provided by list_themes().
99 system_rebuild_theme_data();
101 // Retrieve the option values from the drush command.
102 $destination = drush_get_option('destination', 'sites/all');
103 $starterkit = drush_get_option('starterkit', 'omega_starterkit');
105 if ($machine_name = drush_get_option('machine-name')) {
106 // Validate the machine-readable name of the theme.
107 if (!preg_match('/^[a-z][a-z0-9_]*$/', $machine_name)) {
108 return drush_set_error('OMEGA_THEME_NAME_INVALID', dt('The machine name is invalid. It may only contain lowercase numbers, letters and underscores and must start with a letter.'));
111 $themes = list_themes();
112 // Validate that the machine-readable name of the theme is unique.
113 if (isset($themes[$machine_name])) {
114 return drush_set_error('OMEGA_THEME_ALREADY_EXISTS', dt('A theme with that name already exists. The machine-readable name must be unique.'));
118 // Check if the destination is valid.
119 if (!omega_drush_resolve_destination($destination)) {
120 return drush_set_error('OMEGA_DESITINATION_INVALID', dt('The destination is invalid.'));
123 // Check if the chosen base theme or starterkit exists.
124 if (!array_key_exists($starterkit, omega_drush_starterkits())) {
125 return drush_set_error('OMEGA_STARTERKIT_NOT_EXISTS', dt('There is no valid Omega theme starterkit with the name @starterkit.', array('@starterkit' => $starterkit)));
128 if ($basetheme = drush_get_option('basetheme')) {
129 if (!array_key_exists($basetheme, list_themes()) || !array_key_exists('omega', omega_theme_trail($basetheme))) {
130 return drush_set_error('OMEGA_BASETHEME_INVALID', dt('The base theme @basetheme does not exist or is invalid.', array('@basetheme' => $basetheme)));
136 * Implements drush_hook_COMMAND().
138 function drush_omega_subtheme($name = NULL
) {
140 drush_prompt(dt('Please enter the name of the new sub-theme'));
143 $destination = drush_get_option('destination', 'sites/all');
145 $subtheme = new
stdClass();
146 $subtheme->name
= $name;
147 $subtheme->machine_name
= drush_get_option('machine-name', _omega_drush_generate_theme_name($name));
148 $subtheme->path
= omega_drush_resolve_destination($destination) .
'/' .
$subtheme->machine_name
;
149 $subtheme->starterkit
= drush_get_option('starterkit', 'omega_starterkit');
150 $subtheme->default
= drush_get_option('set-default') !== NULL
;
151 $subtheme->enable
= $subtheme->default
|| drush_get_option('enable') !== NULL
;
152 $subtheme->basetheme
= drush_get_option('basetheme');
154 if (omega_drush_subtheme_create($subtheme)) {
155 drush_log(dt('You have successfully created the theme @theme (@name) in @path.', array(
156 '@theme' => $subtheme->name
,
157 '@name' => $subtheme->machine_name
,
158 '@path' => dirname($subtheme->path
),
164 * Resolves the destination path for a subtheme. This can either be a profile
165 * theme folder, a sites theme folder or a theme.
167 * @param $destination
168 * A destination string, this can either be a site path ('sites/foo'), a
169 * profile path ('profiles/foo') or a theme path ('themes/foo').
171 * @return string|bool
172 * The full path to the given destination, FALSE if the destination could not
175 function omega_drush_resolve_destination($destination) {
176 if (strpos($destination, 'sites/') === 0) {
177 if (in_array(substr($destination, 6), omega_drush_sites())) {
178 return $destination .
'/themes';
181 elseif (strpos($destination, 'profiles/') === 0) {
182 require_once DRUPAL_ROOT .
'/includes/install.core.inc';
184 $profile = substr($destination, 9);
185 $profiles = install_find_profiles();
186 if (array_key_exists($profile, $profiles)) {
187 return dirname($profiles[$profile]->uri
) .
'/themes';
190 elseif (strpos($destination, 'themes/') === 0) {
191 $theme = substr($destination, 7);
193 if (array_key_exists($theme, list_themes())) {
194 return drupal_get_path('theme', $theme);
202 * Implements drush_hook_COMMAND_validate().
204 function drush_omega_export_validate($theme = NULL
) {
205 $themes = list_themes();
206 // Check if the given theme exists.
207 if (isset($theme) && !isset($themes[$theme])) {
208 return drush_set_error('OMEGA_THEME_NOT_EXISTS', dt('There is no theme with that name.'));
213 * Implements drush_hook_COMMAND().
215 * Exports the theme settings for the given theme from the database and writes
216 * them into the .info file of that theme.
219 * (optional) The machine-readable name of a theme.
221 * TRUE on success, FALSE on failure.
223 function drush_omega_export($theme = NULL
) {
224 if (!isset($theme) && !$theme = _omega_drush_theme_choice()) {
228 $themes = list_themes();
230 // Insert the theme settings from the database.
231 if ($settings = variable_get('theme_' .
$theme .
'_settings')) {
232 foreach (_system_default_theme_features() as
$feature) {
233 // Remove the 'toggle_foobar' elements from the theme settings array.
234 unset($settings['toggle_' .
$feature]);
237 elseif (!drush_confirm(dt('There are no theme settings for @theme stored in the database. Do you want to purge the theme settings from the .info file too?', array('@theme' => $themes[$theme]->info
['name'])))) {
241 // Parse the current content of the .info file so we can append the settings
242 // from the database.
243 $path = drupal_get_path('theme', $theme) .
'/' .
$theme .
'.info';
244 $data = file_get_contents($path);
246 // Remove the old theme settings from the .info file.
247 $data = trim(preg_replace('/^settings\[.*\].*\n?/mi', '', $data));
249 // Append the exported theme settings to the .info file if there are any.
250 $data = $settings ?
$data .
"\n\n" .
omega_drush_build_info_file($settings, 'settings') : $data;
252 // Write the data to the .info file of the theme.
253 if (file_unmanaged_save_data($data, $path, FILE_EXISTS_REPLACE
)) {
254 drush_log(dt('The theme settings for the @theme theme have been exported to the .info file of the theme.', array('@theme' => $themes[$theme]->info
['name'])), 'success');
256 if (drush_get_option('revert')) {
257 // Revert the theme settings if the 'revert' option is set and they have
258 // been exported successfully. In this case, we invoke the API function
259 // through the drush command to get the print messages displazed.
260 drush_op('drush_omega_revert', $theme);
266 // There was an error while exporting the theme settings.
267 return drush_set_error('OMEGA_EXPORT_ERROR', dt('An error occurred while trying to export the theme settings for the @theme theme.', array('@theme' => $themes[$theme]->info
['name'])));
272 * Implements drush_hook_COMMAND_validate().
274 function drush_omega_revert_validate($theme) {
275 $themes = list_themes();
276 // Check if the given theme exists.
277 if (isset($theme) && !isset($themes[$theme])) {
278 return drush_set_error('OMEGA_THEME_NOT_EXISTS', dt('There is no theme with that name.'));
283 * Implements drush_hook_COMMAND().
285 * Delete the theme settings that have been stored in the database and thereby
286 * reverts them to the default settings from the .info file.
289 * The machine-readable name of a theme.
291 function drush_omega_revert($theme = NULL
) {
292 if (!isset($theme) && !$theme = _omega_drush_theme_choice()) {
296 $themes = list_themes();
297 // Delete the theme settings variable for the given theme.
298 variable_del('theme_' .
$theme .
'_settings');
300 // Clear the theme settings and extensions cache.
301 cache_clear_all('theme_settings:' .
$theme, 'cache');
302 cache_clear_all('omega_extensions:' .
$theme, 'cache');
304 // Rebuild the theme data for good measure.
305 drupal_theme_rebuild();
306 system_rebuild_theme_data();
308 drush_log(dt('You have successfully reverted the theme settings for the @theme theme.', array('@theme' => $themes[$theme]->info
['name'])), 'success');
312 * Helper function for printing a list of available themes that the user can
313 * choose from in a drush prompt.
315 * @return bool|string
316 * The machine-readable name of the chosen theme or FALSE if the operation was
319 function _omega_drush_theme_choice() {
320 $themes = list_themes();
321 $values = array_values($themes);
323 foreach ($values as
$key => $info) {
324 $options[$key] = $info->info
['name'];
327 if (!$theme = drush_choice($options, dt('Which theme do you want to export the theme settings for?'))) {
331 return $values[$theme]->name
;
335 * Retrieves all themes that can be used as starterkits for a given base theme.
338 * (Optional) The machine-readable name of a base theme to filter all the
339 * available starterkits for.
342 * An array of theme names keyed with the machine-readable name of each theme.
344 function omega_drush_starterkits($base = NULL
) {
347 // Retrieve all the possible starterkits.
348 foreach (list_themes() as
$name => $theme) {
349 $info = $theme->info
;
351 // A starterkit is always flagged as such in the .info file.
352 if (!empty($info['starterkit']) && !empty($info['base theme']) && (!isset($base) || $info['base theme'] == $base)) {
353 $options[$name] = $info['name'] .
(!empty($info['description']) ?
': ' .
$info['description'] : '');
361 * Recursively rewrites (and renames) all files in a given path.
364 * The path to rewrite all files in.
366 * The string(s) to look for when replacing the file names and contents. Can
367 * be an array or a string.
369 * The string(s) to replace $search with. Can be an array or a string.
372 * TRUE if the operation succeeded, FALSE otherwise.
374 * @see omega_drush_replace_contents()
377 function omega_drush_rewrite_recursive($path, $search, $replace) {
378 if ($path !== ($new = str_replace($search, $replace, $path))) {
379 // First, try to rename (move) the file if the name was changed.
380 if (!drush_move_dir($path, $new, TRUE
)) {
386 // If the file actually is a directory, proceed with the recursion.
387 $directory = dir($new);
389 while (FALSE
!== ($read = $directory->read())) {
390 if ($read != '.' && $read != '..' ) {
391 if (!omega_drush_rewrite_recursive($new .
'/' .
$read, $search, $replace)) {
399 elseif (is_file($new)) {
400 // If it is a file, try to replace its contents.
401 $before = file_get_contents($new);
402 if ($before !== ($after = str_replace($search, $replace, $before))) {
403 if (file_unmanaged_save_data($after, $new, FILE_EXISTS_REPLACE
) === FALSE
) {
413 * Recursively builds an .info file structure from an array.
416 * The array to build the .info file from.
418 * (Optional) Used internally to forward the current prefix (level of nesting)
422 * A .info file string.
424 function omega_drush_build_info_file($array, $prefix = FALSE
) {
427 foreach ($array as
$key => $value) {
428 if (is_array($value)) {
429 // This is an array, let's proceed with the next level.
430 $info .
= omega_drush_build_info_file($value, (!$prefix ?
$key : "{$prefix}[{$key}]"));
433 // Escape all single quotes.
434 $value = str_replace("'", "\'", $value);
435 // Wrap the value in single quotes if it has any trailing or leading
436 // whitespace or it is an empty string from the start.
437 $value = $value === '' || trim($value) != $value ?
"'" .
$value .
"'" : $value;
438 // If the key is numeric remove it entirely (simple brackets are enough in
440 $key = is_numeric($key) ?
'' : $key;
442 $info .
= $prefix ?
("{$prefix}[" .
$key .
']') : $key;
443 $info .
= ' = ' .
$value .
"\n";
451 * Helper function for creating and saving a .info file.
454 * The name of the .info file (equivalent to the machine-readable name of the
455 * theme that the .info file belongs to).
457 * The content of the .info file.
458 * @param $destination
459 * (Optional) The path in which the .info file should be saved. Defaults to
460 * the path of the theme as defined by $name.
463 * TRUE if the .info file was successfully saved, FALSE otherwise.
465 function omega_drush_write_info_file($name, $data, $destination = NULL
) {
466 $destination = isset($destination) ?
$destination : drupal_get_path('theme', $name);
468 if (!empty($destination)) {
469 $data = is_array($data) ?
omega_drush_build_info_file($data) : $data;
470 return file_unmanaged_save_data($data, $destination .
'/' .
$name .
'.info', FILE_EXISTS_REPLACE
);
477 * Creates a subtheme from any given starterkit.
480 * An object containing all the required definitions for creating a subtheme.
481 * The available properties are:
483 * - 'name': The human-readable name of the subtheme.
484 * - 'machine_name': The machine-readable name of the subtheme.
485 * - 'path': The destination that the subtheme should be placed in relative to
487 * - 'starterkit': The machine-readable name of the starterkit that should be
488 * used for creating the subtheme.
489 * - 'default': (optional) Whether the subtheme should be enabled and set as
490 * the default theme after it has been created.
491 * - 'enable': (ptional) Whether the subtheme should be enabled after it has
495 * TRUE if the subtheme could be successfully created, FALSE otherwise.
497 function omega_drush_subtheme_create($subtheme) {
498 // Check whether the destination path does not exist and bail out if it does
499 // so we don't delete any important data by accident.
500 if (is_dir($subtheme->path
)) {
501 return drush_set_error('OMEGA_SUBTHEME_PATH', dt('The path @path already exists.', array('@path' => $subtheme->path
)));
504 $destination = dirname($subtheme->path
);
505 if (!is_writable($destination) && !file_prepare_directory($destination, FILE_CREATE_DIRECTORY
| FILE_MODIFY_PERMISSIONS
)) {
506 return drush_set_error('OMEGA_SUBTHEME_PATH', dt('The path @path is not writable.', array('@path' => $destination)));
509 $themes = list_themes();
510 // Try to copy the starterkit to the destination path of the new subtheme.
511 $source = drupal_get_path('theme', $subtheme->starterkit
);
512 if (!drush_copy_dir($source, $subtheme->path
)) {
513 return drush_set_error('OMEGA_STARTERKIT', t('The starterkit @starterkit could not be copied.', array('@starterkit' => $themes[$subtheme->starterkit
]->info
['name'])));
516 // Recursively rewrite the file names and contents of all the files that are
517 // now in the subtheme's directory to represent the human- and
518 // machine-readable names of the subtheme.
519 $search = array($subtheme->starterkit
, $themes[$subtheme->starterkit
]->info
['name']);
520 $replace = array($subtheme->machine_name
, $subtheme->name
);
521 omega_drush_rewrite_recursive($subtheme->path
, $search, $replace);
523 // Parse the contents of the current .info file (provided by the starterkit).
524 $subtheme->info
= drupal_parse_info_file($subtheme->path .
'/' .
$subtheme->machine_name .
'.info');
525 // Unset all unneeded (and potentially hindering) properties in the .info file
527 unset($subtheme->info
['starterkit'], $subtheme->info
['hidden'], $subtheme->info
['locked'], $subtheme->info
['project'], $subtheme->info
['datestamp']);
529 // Put the name and description, as well as a 'dummy' version for the new
530 // subtheme in place.
531 $subtheme->info
['name'] = $subtheme->name
;
532 $subtheme->info
['description'] = $subtheme->description
;
533 $subtheme->info
['version'] = '1.x';
535 if (!empty($subtheme->basetheme
)) {
536 $subtheme->info
['base theme'] = $subtheme->basetheme
;
539 // Overwrite the existing .info file with the changed values.
540 omega_drush_write_info_file($subtheme->machine_name
, $subtheme->info
, $subtheme->path
);
542 // Rebuild the theme caches so that we can do some final tasks.
543 drupal_theme_rebuild();
544 system_rebuild_theme_data();
546 if (!empty($subtheme->enable
) || !empty($subtheme->default
)) {
547 // Enable the subtheme.
548 theme_enable(array($subtheme->machine_name
));
550 if (!empty($subtheme->default
)) {
551 // Make the newly created subtheme the default theme.
552 variable_set('theme_default', $subtheme->machine_name
);
560 * Retrieve an array of available sites from the sites.php.
563 * An array that contains all the available sites in a multisite environment.
565 function omega_drush_sites() {
566 $return = array('all' => 'all', 'default' => 'default');
568 // Load the available sites (multisite installation) from the sites.php file
570 if (file_exists(DRUPAL_ROOT .
'/sites/sites.php')) {
571 include DRUPAL_ROOT .
'/sites/sites.php';
574 $return = array_merge($return, $sites);
578 // The 'all' and 'default' destinations are always available.
579 return array_keys($return);
583 * Helper function for generating a valid machine-readable name for a theme from
587 * The string to generate the machine-readable name from.
590 * The generated machine-readable name.
592 function _omega_drush_generate_theme_name($string) {
593 // Machine-readable names have to start with a lowercase letter.
594 $string = preg_replace('/^[^a-z]+/', '', strtolower($string));
595 // Machine-readable names may only contain alphanumeric characters and
597 $string = preg_replace('/[^a-z0-9_]+/', '_', $string);
598 // Trim all trailing and leading underscores.
599 $string = trim($string, '_');
601 $themes = list_themes();
602 if (isset($themes[$string])) {
606 while (isset($themes[$string])) {
607 // Make sure that the machine-readable name of the theme is unique.
608 $string = $plain .
'_' .
$counter++;