<?php /* Copyright 2011-2019 Xavier Guerrin and the TuxFamily team This file is part of the tuxfamily_repository module. This module is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This module is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this module. If not, see <http://www.gnu.org/licenses/>. */ /** * @file * Implements the core hooks, defines, public and private functions. */ define('TUXFAMILY_REPOSITORY_ENABLED_VARIABLE', 'tuxfamily_repository_enabled'); define('TUXFAMILY_REPOSITORY_DEFAULT_ENABLED', FALSE); define('TUXFAMILY_REPOSITORY_PROJECT_VARIABLE', 'tuxfamily_project'); define('TUXFAMILY_REPOSITORY_DEFAULT_PROJECT', 'project'); define('TUXFAMILY_REPOSITORY_DRUPAL_STATIC_PATH_VARIABLE', 'tuxfamily_repository_drupal_static_path'); define('TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_STATIC_PATH', 'drupal_files/static'); define('TUXFAMILY_REPOSITORY_DRUPAL_FILES_PATH_VARIABLE', 'tuxfamily_repository_drupal_files_path'); define('TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_FILES_PATH', 'drupal_files/sites/@sitename/files'); define('TUXFAMILY_REPOSITORY_BASE_URL_PATTERN_VARIABLE', 'tuxfamily_repository_base_url_pattern'); define('TUXFAMILY_REPOSITORY_DEFAULT_BASE_URL_PATTERN', 'https://download.tuxfamily.org/@project/@path'); define('TUXFAMILY_REPOSITORY_DRUPAL_PATH_PATTERN_VARIABLE', 'tuxfamily_repository_drupal_path_pattern'); define('TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_PATH_PATTERN', '/data/repository/@project/@path'); define('TUXFAMILY_REPOSITORY_SSH_PATH_PATTERN_VARIABLE', 'tuxfamily_repository_ssh_path_pattern'); define('TUXFAMILY_REPOSITORY_DEFAULT_SSH_PATH_PATTERN', '/home/@project/@project-repository/@path'); define('TUXFAMILY_REPOSITORY_RSYNC_PATH_VARIABLE', 'tuxfamily_repository_rsync_path'); define('TUXFAMILY_REPOSITORY_DEFAULT_RSYNC_PATH', '/usr/bin/rsync'); /** * Implements hook_boot(). * Used to adjust the public_file_path variable in case Drupal is run from * TuxFamily's SSH access. */ function tuxfamily_repository_boot() { // Not configurable yet, this arbitrary condition checks whether we are run through SSH / Cron if (isset($_SERVER['HOME']) && $_SERVER['HOME'] == '/home') { $GLOBALS['conf']['tuxfamily_ssh'] = TRUE; $GLOBALS['conf']['file_public_path'] = _tuxfamily_repository_get_files_base_path(); } } /** * Implements hook_modules_installed(). * Used here to sync static files to the TuxFamily repository. */ function tuxfamily_repository_modules_installed($modules_installed) { // do nothing when the module itself is enabled/installed (i.e. probably not configured yet) if (in_array('tuxfamily_repository', $modules_installed)) { return; } _tuxfamily_repository_rsync_static_files(); } /** * Implements hook_file_url_alter(). * Used here to modify generated file URLs. */ function tuxfamily_repository_file_url_alter(&$path) { $enabled = variable_get(TUXFAMILY_REPOSITORY_ENABLED_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_ENABLED); if (!$enabled) { return; } $data = drupal_static(__FUNCTION__); if (!$data) { $data['static_url'] = _tuxfamily_repository_get_static_base_url(); $data['files_url'] = _tuxfamily_repository_get_files_base_url(); $data['files_path'] = _tuxfamily_repository_get_files_base_path(); } // We do not alter private files if (preg_match('/^(private|http):\/\//', $path)) { return; } // We have to handle a special case: /styles/ pictures are generated the // first time they are accessed -- therefore, if the file does not exist on // the download repository, we must output a URL that will trigger this // generation. $matches = array(); if (preg_match('/^public:\/\/styles\/(.+)$/', $path, $matches)) { if (!file_exists($path)) { $path = $GLOBALS['base_url'] . '/system/files/styles/' . $matches[1]; return; } } // Eventually, we replace public:// (9 characters) with our base url. if (preg_match('/^public:.*$/', $path)) { $path = $data['files_url'] . '/' . substr($path, 9); } else { $path = $data['static_url'] . '/' . ltrim($path, '/'); } } /** * Implements hook_menu(). */ function tuxfamily_repository_menu() { $items['admin/config/tuxfamily'] = array( 'title' => 'TuxFamily repository', 'page callback' => 'drupal_get_form', 'page arguments' => array('_tuxfamily_repository_config'), 'access arguments' => array('administer site configuration'), 'type' => MENU_CALLBACK, ); return $items; } /** * Implements hook_form_alter(). */ function tuxfamily_repository_form_alter(&$form, &$form_state, $form_id) { if ($form_id == 'system_file_system_settings') { _tuxfamily_repository_alter_file_system_settings($form, $form_state, $form_id); } } /** * @return the pattern to be used in order to determine the absolute path to * the TuxFamily repository. Note the result depends on the environment: web * server or SSH server. */ function _tuxfamily_repository_get_repository_path_pattern() { if ($GLOBALS['conf']['tuxfamily_ssh']) { $path = variable_get(TUXFAMILY_REPOSITORY_SSH_PATH_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_SSH_PATH_PATTERN); } else { $path = variable_get(TUXFAMILY_REPOSITORY_DRUPAL_PATH_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_PATH_PATTERN); } return $path; } /** * @return the path, relative to the TuxFamily repository, to the directory * that will host static content, i.e. content provided by Drupal modules. */ function _tuxfamily_repository_get_static_path() { $static_path = variable_get(TUXFAMILY_REPOSITORY_DRUPAL_STATIC_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_STATIC_PATH); $static_path = _tuxfamily_repository_apply_tokens($static_path); return $static_path; } /** * @return the path, relative to the TuxFamily repository, to the directory * that will host the Drupal files, i.e. uploaded and dynamically generated files. */ function _tuxfamily_repository_get_files_path() { $files_path = variable_get(TUXFAMILY_REPOSITORY_DRUPAL_FILES_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_FILES_PATH); $files_path = _tuxfamily_repository_apply_tokens($files_path); return $files_path; } /** * @return the base URL to the directory that will host the Drupal files, i.e. * uploaded and dynamically generated files. */ function _tuxfamily_repository_get_files_base_url() { $url = variable_get(TUXFAMILY_REPOSITORY_BASE_URL_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_BASE_URL_PATTERN); $url = _tuxfamily_repository_apply_tokens($url, array('@path' => _tuxfamily_repository_get_files_path())); return $url; } /** * @return the base URL to the directory that will host static content, i.e. * content provided by Drupal modules. */ function _tuxfamily_repository_get_static_base_url() { $url = variable_get(TUXFAMILY_REPOSITORY_BASE_URL_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_BASE_URL_PATTERN); $url = _tuxfamily_repository_apply_tokens($url, array('@path' => _tuxfamily_repository_get_static_path())); return $url; } /** * @return the absolute path to the directory that will host the Drupal files, * i.e. uploaded and dynamically generated files. */ function _tuxfamily_repository_get_files_base_path() { $path = _tuxfamily_repository_get_repository_path_pattern(); $path = _tuxfamily_repository_apply_tokens($path, array('@path' => _tuxfamily_repository_get_files_path())); return $path; } /** * @return the absolute path to the directory that will host static content, i.e. * content provided by Drupal modules. */ function _tuxfamily_repository_get_static_base_path() { $path = _tuxfamily_repository_get_repository_path_pattern(); $path = _tuxfamily_repository_apply_tokens($path, array('@path' => _tuxfamily_repository_get_static_path())); return $path; } /** * Modify the 'file_system_settings' form to let the user enable and configure * its TuxFamily repository. */ function _tuxfamily_repository_alter_file_system_settings(&$form, &$form_state, $form_id) { // we add our own submit function $form['#submit'][] = '_tuxfamily_repository_submit_file_system_settings'; // should we work? $tf_repos_enabled = variable_get(TUXFAMILY_REPOSITORY_ENABLED_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_ENABLED); if ($tf_repos_enabled) { $form['file_public_path']['#default_value'] = _tuxfamily_repository_get_files_base_path(); $form['file_public_path']['#description'] .= ' ' . t('Note its value is computed by the TuxFamily repository module'); } $form[TUXFAMILY_REPOSITORY_ENABLED_VARIABLE] = array( '#type' => 'checkbox', '#title' => t('Use TuxFamily download repository for public files'), '#default_value' => $tf_repos_enabled, '#weight' => -1000, ); // usual options $form['tuxfamily_repository_options'] = array( '#type' => 'fieldset', '#title' => 'TuxFamily repository options', '#collapsible' => TRUE, '#collapsed' => !$tf_repos_enabled, '#weight' => -999, ); $form['tuxfamily_repository_options']['tokens'] = array( '#markup' => 'The following tokens are available for paths: @sitename (e.g. default if you are not using a multisite setup), @drupalroot (path to the Drupal directory) and @project (value filled in the first field).', ); $form['tuxfamily_repository_options'][TUXFAMILY_REPOSITORY_PROJECT_VARIABLE] = array( '#type' => 'textfield', '#title' => t('Name of your TuxFamily project'), '#description' => t('Your TuxFamily repository usually has the same name as its parent project. Please enter this name here.'), '#required' => TRUE, '#default_value' => variable_get(TUXFAMILY_REPOSITORY_PROJECT_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_PROJECT), ); $form['tuxfamily_repository_options'][TUXFAMILY_REPOSITORY_DRUPAL_FILES_PATH_VARIABLE] = array( '#type' => 'textfield', '#title' => t('Directory for your site\'s files in your TuxFamily repository'), '#description' => t('Which subdirectory within your TuxFamily repository should be used to store files uploaded to your website?'), '#required' => TRUE, '#default_value' => variable_get(TUXFAMILY_REPOSITORY_DRUPAL_FILES_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_FILES_PATH), ); $form['tuxfamily_repository_options'][TUXFAMILY_REPOSITORY_DRUPAL_STATIC_PATH_VARIABLE] = array( '#type' => 'textfield', '#title' => t('Directory for Drupal static files in your TuxFamily repository'), '#description' => t('Which subdirectory within your TuxFamily repository should be used to store static resources (CSS, JS, images, ...) provided by Drupal and its modules?'), '#required' => TRUE, '#default_value' => variable_get(TUXFAMILY_REPOSITORY_DRUPAL_STATIC_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_STATIC_PATH), ); $form['tuxfamily_repository_options']['tuxfamily_repository_sync_instructions'] = array( '#markup' => sprintf(t('You may rsync Drupal static files on-demand using <a href="%s">%s</a>.'), url('admin/config/tuxfamily'), 'admin/config/tuxfamily'), ); // advanced options $form['tuxfamily_repository_advanced_options'] = array( '#type' => 'fieldset', '#title' => 'TuxFamily repository advanced options', '#description' => 'Unless TuxFamily has modified their architecture, you should not need to change these settings.', '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => -998, ); $form['tuxfamily_repository_advanced_options']['tokens'] = array( '#markup' => 'The following tokens are available for paths/URLs: @sitename (e.g. default if you are not using a multisite setup), @drupalroot (path to the Drupal directory) and @project (your TuxFamily project short name), @path (path that will be appended, to be considered mandatory).', ); $form['tuxfamily_repository_advanced_options'][TUXFAMILY_REPOSITORY_BASE_URL_PATTERN_VARIABLE] = array( '#type' => 'textfield', '#title' => t('Base URL of your TuxFamily repository'), '#description' => t('This pattern is used to compose the complete base URL to your TuxFamily repository.'), '#required' => TRUE, '#default_value' => variable_get(TUXFAMILY_REPOSITORY_BASE_URL_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_BASE_URL_PATTERN), ); $form['tuxfamily_repository_advanced_options'][TUXFAMILY_REPOSITORY_DRUPAL_PATH_PATTERN_VARIABLE] = array( '#type' => 'textfield', '#title' => t('Absolute path to the Drupal-dedicated directory in your TuxFamily repository, as seen on web servers'), '#description' => t('This pattern is used to compose the absolute path to the directory that will store your Drupal files within your TuxFamily repository.'), '#required' => TRUE, '#default_value' => variable_get(TUXFAMILY_REPOSITORY_DRUPAL_PATH_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_DRUPAL_PATH_PATTERN), ); $form['tuxfamily_repository_advanced_options'][TUXFAMILY_REPOSITORY_SSH_PATH_PATTERN_VARIABLE] = array( '#type' => 'textfield', '#title' => t('Absolute path to the Drupal-dedicated directory in your TuxFamily repository, as seen through SSH'), '#description' => t('Path to the same directory as seen through SSH (useful for cron jobs).'), '#required' => TRUE, '#default_value' => variable_get(TUXFAMILY_REPOSITORY_SSH_PATH_PATTERN_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_SSH_PATH_PATTERN), ); $form['tuxfamily_repository_advanced_options'][TUXFAMILY_REPOSITORY_RSYNC_PATH_VARIABLE] = array( '#type' => 'textfield', '#title' => t('Rsync command'), '#description' => t('Path to the rsync tool.'), '#required' => TRUE, '#default_value' => variable_get(TUXFAMILY_REPOSITORY_RSYNC_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_RSYNC_PATH), ); } /** * Describe the _tuxfamily_repository_config form, which allows administrators * to rsync static files to their TuxFamily repository on-demand. */ function _tuxfamily_repository_config($form, &$form_state) { $form['instructions'] = array( '#markup' => t('This page allows you to manually rsync Drupal static files to your TuxFamily repository. Use it with caution since it does not implement a lock yet, which could lead to multiple executions of rsync at the same time.') . '<br /><br />', ); $form['go'] = array( '#type' => 'submit', '#value' => 'Go', ); return $form; } /** * Handle submission of the _tuxfamily_repository_config form. */ function _tuxfamily_repository_config_submit($form, &$form_state) { _tuxfamily_repository_rsync_static_files(); } /** * Handle part of the submission of the file_system_settings form. * @see _tuxfamily_repository_alter_file_system_settings() */ function _tuxfamily_repository_submit_file_system_settings($form, $form_state) { $tf_repos_enabled = variable_get(TUXFAMILY_REPOSITORY_ENABLED_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_ENABLED); if ($tf_repos_enabled) { variable_set('file_public_path', _tuxfamily_repository_get_files_base_path()); } } /** * @return the list of directories that should be deported to the TuxFamily * repository for this module to be efficient. */ function _tuxfamily_repository_deported_directories() { return array('misc', 'modules', 'themes', 'sites/all'); } /** * Copy all directories returned by _tuxfamily_repository_deported_directories * to the Tuxfamily repository using rsync. * @return true if everything went ok, false otherwise. */ function _tuxfamily_repository_rsync_static_files() { $tf_repos_enabled = variable_get(TUXFAMILY_REPOSITORY_ENABLED_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_ENABLED); if (!$tf_repos_enabled) { return FALSE; } if (!_tuxfamily_repository_check_rsync_presence()) { drupal_set_message(t('Unable to find the rsync tool'), 'error'); return FALSE; } foreach (array('misc', 'modules', 'themes', 'sites/all') as $subset) { $exec = _tuxfamily_repository_rsync_subset($subset); if ($exec['code'] !== 0) { // avoid displaying too much information $output = drupal_strlen($exec['output']) > 1024 ? drupal_substr(0, 1024, $exec['output']) : $exec['output']; drupal_set_message(t('An error occured when syncing @subset to your TuxFamily download repository: @output', array('@subset' => $subset, '@output' => $exec['output'])), 'error'); return FALSE; } } drupal_set_message(t('Static files have been rsync\'ed to your TuxFamily repository')); return TRUE; } /** * @return true if the rsync tool is present, false otherwise. */ function _tuxfamily_repository_check_rsync_presence() { // this module simply expects to find the rsync tool in the PATH return file_exists(variable_get(TUXFAMILY_REPOSITORY_RSYNC_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_RSYNC_PATH)); } /** * @return the exclude options to be used within rsync commands. */ function _tuxfamily_repository_exclude_options() { $options = ''; foreach (array('php', 'inc', 'module', 'install', 'test', 'info') as $ext) { $options .= sprintf(' --exclude=\'*.%s\'', $ext); } return $options; } /** * Copy a given directory to the TuxFamily repository using rsync. * @param $subset Subdirectory to be rsync'ed. */ function _tuxfamily_repository_rsync_subset($subset) { $static_root = _tuxfamily_repository_get_static_base_path(); if (!is_dir($static_root)) { if (!mkdir($static_root)) { drupal_set_message(t('Unable to create directory @root.', array('@root' => $static_root)), 'error'); return FALSE; } } $command = _tuxfamily_repository_apply_tokens( '@rsync --delete --archive --quiet @exclude_options --relative @drupal_root/./@subset @repository_static_root', array( '@subset' => $subset, '@repository_static_root' => $static_root, '@exclude_options' => _tuxfamily_repository_exclude_options(), ) ); drupal_set_time_limit(120); return _tuxfamily_repository_execute_command($command); } /** * @return the base name of the directory hosting this site's configuration * file */ function _tuxfamily_repository_site_name() { return basename(conf_path()); } /** * @return the base tokens available when interpreting paths configured for * this module. */ function _tuxfamily_repository_default_tokens() { return array( '@drupal_root' => DRUPAL_ROOT, '@sitename' => _tuxfamily_repository_site_name(), '@project' => variable_get(TUXFAMILY_REPOSITORY_PROJECT_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_PROJECT), '@rsync' => variable_get(TUXFAMILY_REPOSITORY_RSYNC_PATH_VARIABLE, TUXFAMILY_REPOSITORY_DEFAULT_RSYNC_PATH), ); } /** * @param $string String to be interpreted; tokens from * _tuxfamily_repository_default_tokens will be replaced * @param $extra_tokens List of extra tokens that should be replaced in $string * @return The string with all known and provided tokens replaced */ function _tuxfamily_repository_apply_tokens($string, $extra_tokens = array()) { $tokens = _tuxfamily_repository_default_tokens() + $extra_tokens; foreach ($tokens as $token => $value) { $string = str_replace($token, $value, $string); } return $string; } /** * @param $command Command to be executed * $command_redirections input/output redirections applied to the command (see * popen documentation); default to '< /dev/null 2>&1'. */ function _tuxfamily_repository_execute_command($command, $command_redirections = '< /dev/null 2>&1') { $return = array('command' => $command, 'code' => -1, 'output' => ''); $proc_res = popen($command . ' ' . $command_redirections, 'r'); if ($proc_res === FALSE) return $return; $return['output'] = stream_get_contents($proc_res); $return['code'] = pclose($proc_res); return $return; }