Edit file File name : class.A2OptBase.php Content :<?php /** * A2 Optimized: A2OptBase Class * * class inherited by all other classes in A2 Optimized. * This class defines shared functions generally for dealing with cpanel and the user. * * @author Benjamin Cool * @package A2 Optimized cPanel * * */ if (php_sapi_name() != 'cli') { require_once '/usr/local/cpanel/php/cpanel.php'; } class A2OptBase { /** * cpanel object * * object created by calllig new CPANEL that gives access to the liveAPI * as well as many built in cpanel functions * * @var CPANEL */ public $cpanel; /** * home directory * * the path to the home directory for this cpanel user * * @var string */ public $home; /** * sqlite DB path * * location of the .a2-optimized.db file. * * @var string */ public $a2_db; /** * json file path * * location of the .a2-optimized.json file. * * @var string */ public $json_file; /** * applications detected * * an array of $app variables to be stored and managed. * basically all applications detected by A2 Optimized. * * @see $app * @var array */ public $apps; /** * folder location of docroots * * list of docrots that cpanel has configured and their domains * * @var array */ public $docroots; /** * addon domains * * cpanel addon domains * * @var array */ public $addons; /** * parked domains * * domains added as parked according to cPanel API * * @var array */ public $parked; /** * cache type * * @var string */ public $cache_type; /** * action being performed / API call name * * @var string */ public $action; /** * application info being passed around * * this is the info needed for letting the UI know what to render among other things. * basically a representation of the object similar to $values in our WHMCS classes * * @var array */ public $app; /** * username * * the cpanel username that this script is running for. * this needs to always be sanitized as it is used for * folder structure and locations. * * @var string */ public $username; /** * memcached instance * * an array of information about this accounts memcached instance * * @var array */ public $instance; /** * type of application * * the name of the type of application for the given folder. * if this is null it alerts the system that the application * is not of the suspected type or is broken and cannot be controled by A2 Optimized. * * @var string|null */ public $type; /** * certificates * * a list of the ssl certs cpanel has * * @var array */ public $certs; /** * litespeed * * boolean to flag whether the web server is litespeed or not * * @var bool */ public $litespeed; /** * exit on error * * this flag is used to control whether the script should exit when an error is encountered. * helpful for some cases where you do not want to exit on error like when * being called by the app detection algorithm and you want it to return a null type * * @var true|false */ public $exit_on_error; /** * A2OptBase constructor */ public function __construct() { /** * global reference to the cpanel object * * @var CPANEL */ global $cpanel; $this->exit_on_error = true; if (php_sapi_name() != 'cli') { $this->cpanel = $cpanel = new CPANEL(); $this->username = $this->get_username(); $this->home = $this->get_home(); } else { $this->username = get_current_user(); $this->home = "{$this->username}/home"; } $this->litespeed = false; if (file_exists('/usr/local/lsws/bin/lshttpd')) {//TODO: make sure this is the best way to test for litespeed $this->litespeed = true; } if (!is_dir($this->home)) { $this->error('Your home directory according to cPanel could not be found.'); } $this->a2_db = "{$this->home}/.a2-optimized.db"; $this->check_a2_database(); $this->apps = $this->get_apps(); if (isset($_REQUEST['action'])) { $this->action = $_REQUEST['action']; } $this->type = isset($_REQUEST['type']) ? $_REQUEST['type'] : ''; if (isset($_REQUEST['app'])) { $this->app = json_decode($_REQUEST['app']); } $instance = false; if (isset($_REQUEST['instance'])) { $instance = json_decode($_REQUEST['instance']); } elseif (isset($this->app) && isset($this->app->swiftcache) && isset($this->app->swiftcache->memcached) && isset($this->app->swiftcache->memcached->instance)) { $instance = $this->app->swiftcache->memcached->instance; } if ($instance) { $memcached = new MemcachedInstance(1, $this->username); if ($instance->user && $instance->id) { $memcached = new MemcachedInstance($instance->id, $instance->user); } $this->instance = $memcached; if (isset($this->app) && isset($this->app->swiftcache)) { $this->app->swiftcache->memcached = $memcached; } } } /** * check a2optimized database * * Checks that the A2 Optimized SQLite DB exists for this user. Creates and populates with initial data if not present * * @return true|false */ public function check_a2_database() { ORM::configure('sqlite:' . $this->a2_db); $commands = [ 'CREATE TABLE IF NOT EXISTS apps ( id INTEGER PRIMARY KEY, app_path TEXT NOT NULL, app_docroot TEXT, app_domain TEXT, app_type TEXT, app_folder TEXT, app_title TEXT, app_siteurl TEXT, app_homeurl TEXT, app_loginurl TEXT, app_canlogin INTEGER DEFAULT 0, app_a2optimized INTEGER DEFAULT 0, app_notes TEXT, app_optimize_optimized INTEGER DEFAULT 0, app_optimize_optimized_percent INTEGER DEFAULT 0, app_optimize_percent INTEGER DEFAULT 0, app_swiftcache_turbocache TEXT, app_swiftcache_memcached TEXT, app_optimizations TEXT )', 'CREATE TABLE IF NOT EXISTS app_domains ( id INTEGER PRIMARY KEY, app_id INTEGER NOT NULL, app_domain TEXT, app_domain_isssl INTEGER DEFAULT 0, FOREIGN KEY (app_id) REFERENCES apps(id) ON UPDATE CASCADE ON DELETE CASCADE )', 'CREATE TABLE IF NOT EXISTS a2_info ( id INTEGER PRIMARY KEY, info_name TEXT NOT NULL, info_value TEXT NOT NULL )' ]; foreach ($commands as $command) { $res = ORM::raw_execute($command); } $version = ORM::for_table('a2_info')->where('info_name', 'database_version')->find_one(); if (!$version) { $version = ORM::for_table('a2_info')->create(); $version->info_name = 'database_version'; $version->info_value = '0.1'; $version->save(); } $this->do_db_migrations(); } public function do_db_migrations() { $version = ORM::for_table('a2_info')->where('info_name', 'database_version')->find_one(); if ($version->info_value == '0.1') { $command = ' ALTER TABLE apps ADD COLUMN app_nomemcached INTEGER DEFAULT 0; '; $res = ORM::raw_execute($command); $version->info_value = '0.2'; $version->save(); } if ($version->info_value == '0.2') { $command = ' ALTER TABLE apps ADD COLUMN app_lastupdated TEXT DEFAULT 0; '; $res = ORM::raw_execute($command); $version->info_value = '0.21'; $version->save(); } } /** * is cagefs * * determines if the users environment is caged by checking the value of the php selector in cageFS * * @return true|false */ public function is_cagefs() { if (empty($this->username)) { $this->username = $this->get_username(); } if (file_exists('/usr/sbin/cagefsctl')) { // Lets check if CageFS is enabled for this user @exec('/usr/bin/selectorctl --user-current --user=' . $this->username, $cout, $cret); if (preg_match('/ERROR/is', implode('', $cout))) { //not cageFS return false; } //is cageFS return true; } //not cageFS return false; } /** * URL to Domain * * convert a url into a domain name SLD and TLD string * * @param string $url * @return string */ public function url2domain($url) { $domain = explode('/', str_replace(['http://', 'https://'], ['', ''], $url)); return $domain[0]; } /** * test a connection to a socket * * if a socket is connectable it will return true. false otherwise. * * @param resource $socket * @return true|false */ public function can_connect_to_socket($socket) { if ($connection = socket_create(AF_UNIX, SOCK_STREAM, 0)) { if (!socket_connect($connection, $socket)) { return false; } } else { return false; } return true; } /** * set the flag to exit on error * * @param bool $truthiness */ public function set_exit_on_error($truthiness) { $this->exit_on_error = $truthiness; } /** * optimize * * optimize all the things. * this is a stub and is overridden by application classes that inherit this class. */ public function optimize() { //stub to be used by child classes } /** * get certs * * get a list of all SSL certificates from the cpanel liveAPI. * forces retur of false when called from command line. * * @return mixed|array|false */ public function get_certs() { if (php_sapi_name() != 'cli') { $certs = []; $res = $this->cpanel->api2( 'SSL', 'listcrts' ); foreach ($res['cpanelresult']['data'] as $i => $cert) { $certs[$cert['host']] = [ 'expires' => $cert['expiredate'], 'host' => $cert['host'], 'issuer' => $cert['issuer'] ]; } return $certs; } else { return false; } } /** * wrapper for shell_exec * * determines if user is cage fs or not and executes a command after changing directories if a directory is specified. * commands are not sanitized here. make sure all commands are sanitized before passing them in here. * * @param string $cmd * @param string $dir * @return string */ public function su_exec($cmd, $dir = null) { if (!is_null($dir)) { chdir($dir); } if ($this->is_cagefs()) { return shell_exec("/bin/cagefs_enter.proxied $cmd"); // /sbin/cagefs_enter_user $USERNAME "_command_" } else { return shell_exec($cmd); } } /** * get_username * * use the CPANEL object to determine the username of the current user. * if the script is run from command line, it uses the alternate method of the built in php function get_current_user() * * @return string */ public function get_username() { global $cpanel; if (php_sapi_name() != 'cli') { $this->connect_cpanel(); $user_info = $cpanel->uapi( 'Variables', 'get_user_information', [ 'name-1' => 'user', ] ); $this->username = $this->sanitize($user_info['cpanelresult']['result']['data']['user']); } else { $this->username = get_current_user(); } return $this->username; } /** * get home directory * * determine the path to the users home directory. * * @return string */ public function get_home() { global $cpanel; if (php_sapi_name() != 'cli') { $this->connect_cpanel(); $user_info = $cpanel->uapi( 'Variables', 'get_user_information', [ 'name-1' => 'home', ] ); $this->home = $this->sanitize($user_info['cpanelresult']['result']['data']['home']); } else { $this->home = getcwd(); } return $this->home; } /** * connect to cpanel * * connect to the cpanel live API by getting a new CPANEL object. * overwrites the cpanel property and global cpanel variable. */ public function connect_cpanel() { global $cpanel; if (!isset($this->cpanel) && !isset($cpanel)) { $this->cpanel = $cpanel = new CPANEL(); } } /** * is ssl * * determine if a domain is set up for SSL. * this function is designed to be very versitile and used in many different ways. * allows use of a GET param for the domain or uses the domain passes into the function. * when echo is true it will return a json encoded boolean. * * @param string $domain * @param bool $echo * * @return true|false */ public function is_ssl($domain = null, $echo = false) { global $cpanel; $is_ssl = false; if ($domain == null) { if (isset($_REQUEST['domain'])) { $domain = $_REQUEST['domain']; } } if (php_sapi_name() != 'cli') { header('content-type: text/plain'); if ($domain != null && $domain != '' && $domain !== false) { $this->get_home(); $ssl_info = $cpanel->api2( 'SSLInfo', 'fetchinfo', [ 'domain' => "{$domain}", ] ); if (isset($ssl_info['cpanelresult']) && isset($ssl_info['cpanelresult']['data']) && isset($ssl_info['cpanelresult']['data']['status'])) { $is_ssl = (bool)$ssl_info['cpanelresult']['data']['status']; } elseif (isset($ssl_info['cpanelresult']) && isset($ssl_info['cpanelresult']['data'])) { $is_ssl = (bool)$ssl_info['cpanelresult']['data'][0]['status']; } else { $is_ssl = false; } if ($is_ssl) { $ch = curl_init("https://{$domain}/"); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, 1); $header = curl_exec($ch); /* We check the curl headers and response for the following: Not a parked page Is a 200 response code If it is a redirect, it's ok if it's to the www version of the same domain */ if (!(strpos($header, 'defaultwebpage.cgi') === false)) { $is_ssl = false; } else { $info = curl_getinfo($ch); if ($info['http_code'] != 200 || $info['redirect_count'] != 0) { $is_ssl = false; if ( $info['redirect_url'] == 'https://www' . $domain . '/' ) { $is_ssl = true; } } } } } } else { //not cpanel do nothing } if ($echo) { echo json_encode((bool)$is_ssl); } return (bool)$is_ssl; } /** * error * * error handling function that check if the exit on error property is set and reponds accordingly * * @param string $msg * @param int $code */ public function error($msg, $code = 400) { if ($this->exit_on_error) { header("HTTP/1.1 {$code} Bad Request"); echo json_encode(['error' => $msg], JSON_FORCE_OBJECT); error_log($msg); exit(1); } else { error_log($msg); } } /** * is this cpanel * * verifies whether the script is being run from within cpanel and returns true or false accordingly. * * @return true|false */ public function is_cpanel() { if (php_sapi_name() == 'cli') { return false; } return is_null($this->cpanel); } /** * call cpanel api v1 * * wrapper function for making a call to the cpanel api v1 * * @TODO These api version is deprecated and has been replaced with v2. This method can be removed after cpanel v88 is active * * @param string $module * @param string $action * @param array $params * * @return mixed */ public function callAPI1($module, $action, $params = []) { $ret = $this->cpanel->api1($module, $action, $params); if (!empty($ret['cpanelresult']['result']['errors'])) { throw new SystemException(implode('/', $ret['cpanelresult']['result']['errors']), 0); } return $ret['cpanelresult']['data']['result']; } /** * call cpanel api v2 * * wrapper function for making a call to the cpanel api v2 * * @param string $module * @param string $action * @param array $params * * @return mixed */ public function callAPI2($module, $action, $params = []) { $ret = $this->cpanel->api2($module, $action, $params); if (!empty($ret['cpanelresult']['result']['errors'])) { return $ret['cpanelresult']['result']['errors']; } return $ret['cpanelresult']['data']['result']; } /** * get cpanel header * * use the appropriate method for pulling in the header based on the theme * * @return string */ public function get_cpanel_header() { return $this->cpanel->header(); } /** * get cpanel footer * * use the appropriate method for pulling in the footer based on the theme * * @return string */ public function get_cpanel_footer() { return $this->cpanel->footer(); } /** * set instance for memcached * * set the instance property for the object by reating a ne MemcachedInstance. * uses data passed in from the browser to control this. * this was designed to be used for any number of memcached instances, however it is not used in that fashion. */ public function set_instance() { if (isset($_REQUEST['app'])) { $req_app = json_decode($_REQUEST['app']); if (isset($req_app->swiftcache->memcached)) { $this->instance = new MemcachedInstance($req_app->swiftcache->memcached->id, $req_app->swiftcache->memcached->user); } elseif (isset($this->app) && isset($this->app->swiftcache) && isset($this->app->swiftcache->memcached) && isset($this->app->swiftcache->memcached->id) && isset($this->app->swiftcache->memcached->user)) { $this->instance = new MemcachedInstance($this->app->swiftcache->memcached->id, $this->app->swiftcache->memcached->user); } } } /** * Prepare apps for adding to DB * * writes the apps object to the user's database */ public function add_apps_to_db() { foreach ($this->apps as $path => $app) { $app = (object)$app; if (!isset($app->path)) { unset($this->apps[$path]); } else { if (file_exists("{$app->path}/index.php")) { $this->save_app($app); } else { $this->remove_app_by_path($app->path); } } } $this->prune_apps(); } /** * Save individual app to DB * * @param object $app */ public function save_app($app) { // Get existing app from db $db_app = ORM::for_table('apps')->where('app_path', $app->path)->find_one(); if (!$db_app) { // Don't have this app yet, create $db_app = ORM::for_table('apps')->create(); } $app_swiftcache = false; $app_optimize = false; // Recast nested items as objects if (isset($app->swiftcache)) { $app_swiftcache = (object)$app->swiftcache; } if (isset($app->optimize)) { $app_optimize = (object)$app->optimize; $optimized = $app_optimize->optimized; } else { $optimized = true; } $db_app->app_path = $app->path; $db_app->app_docroot = $app->docroot; $db_app->app_domain = $app->domain; $db_app->app_type = $app->type; $db_app->app_folder = $app->folder; $db_app->app_title = $app->title; $db_app->app_siteurl = $app->siteurl; $db_app->app_homeurl = $app->homeurl; $db_app->app_loginurl = $app->loginurl; $db_app->app_canlogin = $app->can_login; $db_app->app_nomemcached = $app->no_memcached; $db_app->app_a2optimized = $app->a2_optimized; if (is_array($app->notes) && isset($app->notes[0])) { $db_app->app_notes = $app->notes[0]; } if ($app_optimize && isset($app_optimize->optimized) && isset($app_optimize->percent) && isset($app_optimize->optimized_percent)) { $db_app->app_optimize_optimized = $optimized; $db_app->app_optimize_percent = $app_optimize->percent; $db_app->app_optimize_optimized_percent = $app_optimize->optimized_percent; } if (isset($app->swiftcache) && isset($app_swiftcache->turbocache)) { $db_app->app_swiftcache_turbocache = json_encode($app_swiftcache->turbocache); } if (isset($app->swiftcache) && isset($app_swiftcache->memcached)) { $db_app->app_swiftcache_memcached = json_encode($app_swiftcache->memcached); } if (isset($app->optimizations)) { $db_app->app_optimizations = json_encode($app->optimizations); } $db_app->app_lastupdated = time(); $db_app->save(); // Set domain relations foreach ($app->domains as $domain) { $db_domain = ORM::for_table('app_domains') ->where('app_id', $db_app->id) ->where('app_domain', $domain) ->where('app_domain_isssl', 0) ->find_one(); if (!$db_domain) { $db_domain = ORM::for_table('app_domains')->create(); $db_domain->app_id = $db_app->id; $db_domain->app_domain = $domain; $db_domain->app_domain_isssl = 0; $db_domain->save(); } } foreach ($app->ssl_domains as $domain) { $db_domain = ORM::for_table('app_domains') ->where('app_id', $db_app->id) ->where('app_domain', $domain) ->where('app_domain_isssl', 1) ->find_one(); if (!$db_domain) { $db_domain = ORM::for_table('app_domains')->create(); $db_domain->app_id = $db_app->id; $db_domain->app_domain = $domain; $db_domain->app_domain_isssl = 1; $db_domain->save(); } } } /** * Remove apps from the DB that no longer exist * */ public function prune_apps() { $apps = ORM::for_table('apps')->find_many(); foreach ($apps as $app) { if (!file_exists("{$app->app_path}/index.php")) { $this->remove_app_by_path($app->app_path); } } } /** * Remove apps from the DB by path * * @param string $path */ public function remove_app_by_path($path) { $app = ORM::for_table('apps') ->where('app_path', $path) ->find_one(); $app->delete(); } /** * sanitize * * basic string sanitization for directory structure. * * @param string $string * @return string */ public function sanitize($string) { return str_replace([' ', "\t", '..', '*'], '', $string); } /** * check app path * * verifies that the path specified for the app is set and if not, * finds a valid path based on the folder of the app and the home directory. * */ public function check_app_path() { if (!isset($this->app->path)) { if (isset($this->app->folder)) { $this->app->path = "{$this->home}/{$this->app->folder}"; } else { $this->error('Path to application not specified.'); } } if (!is_dir($this->app->path)) { $this->error("{$this->app->path} does not exist."); } } /** * get apps * * fetch the json file for the apps and return the decoded value. * * @return array */ public function get_apps() { $apps_db = ORM::for_table('apps')->find_many(); $apps = []; foreach ($apps_db as $item) { $apps[] = (array)$item; } return $apps; } /** * is not a docroot * * check to see if a path is a docroot according to cpanel * or not by looking in the addons property. * * @return true|false */ public function is_not_docroot($path) { return !in_array($path, $this->addons); } /** * update json * * save the apps to the json file */ public function update_json() { $this->apps = $this->get_apps(); $this->apps = json_decode(file_get_contents($this->json_file), true); $this->apps[$this->app->path] = (object)$this->app; } /** * update app list * * save the apps to the class * replaces update_json */ public function update_app_list() { $this->add_apps_to_db(); $this->apps = $this->get_apps(); } /** * clear apc cache * * this is a stub * */ public function clear_apc_cache() { } /** * enable cron * * use the cpanel live API v2 to create a cron if called from within * cpanel or use crontab if called from command line. * command is not sanitized, make sure it is well written and * sanitized if it uses variables from an external source. * * @param int|string $minute * @param int|string $hour * @param int|string $day * @param int|string $month * @param int|string $weekday * @param string $command */ public function enable_cron($minute, $hour, $day, $month, $weekday, $command) { global $cpanel; if ($this->is_cpanel()) { //set the cron in cPanel $res = $cpanel->api2( 'Cron', 'add_line', [ 'command' => $command, 'minute' => "{$minute}", 'hour' => "{$hour}", 'day' => "{$day}", 'month' => "{$month}", 'weekday' => "{$weekday}", ] ); unset($res); } else { $cron = "{$minute} {$hour} {$day} {$month} {$weekday} $command"; shell_exec("(crontab -l ; echo '{$cron}') | sort | uniq | crontab"); } } /** * disable cron * * use the cpanel live API v2 or crontab to remove a cron * * @param string $search */ public function disable_cron($search) { global $cpanel; if ($this->is_cpanel()) { $id = $this->get_cron_id($search); //set the cron in cPanel $res = $cpanel->api2( 'Cron', 'remove_line', [ 'line' => $id, ] ); unset($res); } else { $cmd = str_replace('/', "\/", preg_quote($search)); shell_exec("crontab -l | sed 's/[^\\n\\r]*{$cmd}[^\\n\\r]*//' | crontab"); } } /** * get cron command * * search for a cron that has been set and return the string representing it. * this uses the cpanel api v2 or crontab if not in cpanel. * * @param string $search * @return string */ public function get_cron_command($search) { global $cpanel; if ($this->is_cpanel()) { $res = $cpanel->api2('Cron', 'listcron'); $res = $res['cpanelresult']['data']; foreach ($res as $i => $line) { $command = $line['command']; if (!(strpos($command, $search) === false)) { return "{$line['minute']} {$line['hour']} {$line['day']} {$line['month']} {$line['weekday']} {$line['command']}"; } } } else { exec('crontab -l', $res); foreach ($res as $i => $line) { if (!(strpos($line, $search) === false)) { return $line; } } } return ''; } /** * debug * * similar to error but logs the message and echos it * * @param string $msg */ public function debug($msg) { error_log($msg); echo "\n$msg\n"; } /** * get cron id * * searches for a cron based on the search criteria and returns the linenumber of the cron * * @param string $search * @return int|false */ public function get_cron_id($search) { global $cpanel; $this->log_action('get_cron_id for ' . $search); if ($this->is_cpanel()) { $this->log_action('get_cron_id is cpanel for ' . $search); $res = $cpanel->api2('Cron', 'listcron'); $res = $res['cpanelresult']['data']; foreach ($res as $i => $line) { $command = $line['command']; $this->log_action('get_cron_id looking at ' . $command . ' for ' . $search); if (!(strpos($command, $search) === false)) { return $line['count']; } } } else { $this->log_action('get_cron_id is not cpanel for ' . $search); exec('crontab -l', $res); foreach ($res as $i => $line) { $this->log_action('get_cron_id looking at ' . $line . ' for ' . $search); if (!(strpos($line, $search) === false)) { return $i; } } } return false; } /** * @param $htaccess_file * @param array|string $rules * @param null $ifmodule * @param null $remove * @return bool */ public function add_htaccess_rules($htaccess_file, $rules = [], $ifmodule = null, $remove = null) { if (!isset($htaccess_file) || is_null($htaccess_file)) { return false; } //make sure .htaccess exists if (!file_exists($htaccess_file)) { if (file_exists("{$this->path}/htaccess.txt")) { copy("{$this->path}/htaccess.txt", $htaccess_file); } else { touch($htaccess_file); } } //make sure that the .htaccess file is writable if (file_exists($htaccess_file) && is_writable($htaccess_file)) { $htaccess = file_get_contents($htaccess_file); if (!is_null($remove)) { foreach ($remove as $i => $rule) { $htaccess = preg_replace("/[\s#]*({$rule}.*\n)/U", '#$1', $htaccess); } } if (is_array($rules)) { //$rules = array_reverse($rules); foreach ($rules as $i => $rule) { $quoted = str_replace('/', "\/", preg_quote($rule)); $htaccess = preg_replace("/[\s#]*{$quoted}[\s]*/U", '', $htaccess); } if (is_null($ifmodule)) { $htaccess = explode("\n", $rules) . "\n{$htaccess}"; } else { $htaccess = preg_replace("/<ifmodule {$ifmodule}>[\s]*<\/ifmodule>/imU", '', $htaccess); $htaccess = "<ifModule {$ifmodule}>\n" . implode("\n", $rules) . "\n</ifModule>\n{$htaccess}"; } } elseif ($rules != '') { $rule = $rules; $quoted = str_replace('/', "\/", preg_quote($rule)); $htaccess = preg_replace("/.*{$quoted}.*/", '', $htaccess); if (!$this->check_htaccess_rules($htaccess, $rule)) { if (is_null($ifmodule)) { $htaccess = " {$rule}\n{$htaccess}"; } else { $htaccess = preg_replace("/<ifmodule {$ifmodule}>[\s]*</ifmodule>/imU", '', $htaccess); $htaccess = "<ifModule {$ifmodule}>{$rule}</ifModule>\n{$htaccess}"; } } } $fh = fopen($htaccess_file, 'w+'); fwrite($fh, $htaccess); fclose($fh); return true; } else { return false; } } /** * comment htaccess rule * * searches an htaccess file for rows based on regular expressions and comments them out and saves the file. * * @param string $htaccess_file * @param array $rules * @return true|false */ public function comment_htaccess_rules($htaccess_file, $rules) { if (!isset($htaccess_file)) { return false; } if (file_exists($htaccess_file) && is_writable($htaccess_file)) { $htaccess = file_get_contents($htaccess_file); if (is_array($rules)) { foreach ($rules as $i => $rule) { $quoted = str_replace('/', "\/", preg_quote($rule)); $new_htaccess = preg_replace("/.*{$quoted}/", "#$rule", $htaccess); if ($new_htaccess != null) { $htaccess = $new_htaccess; } } } elseif ($rules != '') { $rule = $rules; $quoted = str_replace('/', "\/", preg_quote($rule)); $new_htaccess = preg_replace("/.*{$quoted}/", "#$rule", $htaccess); if ($new_htaccess != null) { $htaccess = $new_htaccess; } } $fh = fopen($htaccess_file, 'w+'); fwrite($fh, $htaccess); fclose($fh); return true; } else { return false; } } /** * check htaccess rules * * takes an htaccess file contents and checks to see if all of the given rules are currently implemented. * this handles either an array of htaccess rules or a sigle rule. search is based on regular expressions. * * @param string $htaccess * @param mixed $rules * @param bool $exact_match * @return bool */ public function check_htaccess_rules($htaccess, $rules, $exact_match = false) { if (is_array($rules)) { $count = 0; foreach ($rules as $i => $rule) { if ($exact_match) { if ((strpos("# {$rule}", $htaccess) === false && strpos("#{$rule}", $htaccess) === false)) { if (!(strpos("{$rule}", $htaccess) === false)) { $count++; } } } else { $quoted = str_replace(['/','"'], ["\/",'\\"'], preg_quote($rule)); $matches = []; $matches2 = []; preg_match("/#[\s]*{$quoted}/Ui", $htaccess, $matches); if (count($matches) == 0) { //echo "\n$quoted\n"; preg_match("/{$quoted}/Ui", $htaccess, $matches2); if (count($matches2) >= 1) { $count++; } } else { preg_match("/{$quoted}/Ui", $htaccess, $matches2); if (count($matches) < count($matches2)) { $count++; } } } } return ($count == count($rules)); } elseif ($rules != '') { $rule = $rules; if ($exact_match) { if ((strpos("# {$rule}", $htaccess) === false && strpos("#{$rule}", $htaccess) === false)) { if (!(strpos("{$rule}", $htaccess) === false)) { return true; } } } else { $quoted = str_replace('/', "\/", preg_quote($rule)); preg_match("/#[\s]*{$quoted}/", $htaccess, $matches); if (count($matches) == 0) { if (preg_match("/{$quoted}/", $htaccess, $matches)) { if (count($matches) >= 1) { return true; } } } else { if (preg_match("/{$quoted}/", $htaccess, $matches2)) { if (count($matches) < count($matches2)) { return true; } } } } } return false; } /** * get htacess file * * get the path to the htaccess file for the current app * * @return string */ public function get_htaccess_file() { return "{$this->path}/.htaccess"; } /** * get htaccess * * get the contents of the htaccess file for the current app * * @return string */ public function get_htaccess() { return file_get_contents($this->get_htaccess_file()); } /** * get_prestashop_version * * get the version of the current PrestaShop install. Needed sometimes before setting up PrestaShop object. * * @return string */ public function get_prestashop_version($path) { $ps_ver = '1.6'; if (is_dir($path . '/app')) { /* Detection for only 1.6 and older Prestashop. 1.7 has /app/config */ $ps_ver = '1.7'; } return $ps_ver; } /** * get_drupal_version * * get the version of the current Drupal install. Needed sometimes before setting up Drupal object. * * @return string */ public function get_drupal_version($folder) { if (file_exists("{$folder}/core/includes/bootstrap.inc")) { if (!file_exists("{$folder}/core/includes/database.inc")) { // Drupal 8 introduced the /core/includes folder // Drupal 9 streamlined it, removing many files including database.inc $dr_ver = 9; } else { $dr_ver = 8; } } elseif (file_exists("{$folder}/includes/bootstrap.inc") && filesize("{$folder}/includes/bootstrap.inc") > 59000) { $this->log_action('Drupal filesize of includes/bootstrap.inc: ' . filesize("{$folder}/includes/bootstrap.inc") . ' for ' . $folder); $dr_ver = 7; } else { $dr_ver = 6; } return $dr_ver; } /** * get_drupal_object * * get the drupal application object. * * @return string */ public function get_drupal_object($app) { if ($app->dr_ver == 7) { return new A2OptDrupal($app); } if ($app->dr_ver == 8) { return new A2OptDrupal8($app); } if ($app->dr_ver == 9) { // Will replace in a later ticket, most stuff behaves as Drupal8 return new A2OptDrupal8($app); } } /** * check_app_title * * Checks the app->title value for sanity * * @param string $title * @param obj $app * @return string */ public function check_app_title($title, $app) { if (strlen($title) > 100 || $title == '' || is_null($title)) { $title = $app->domain; } return $title; } /** * log_action * * If support has flagged this account for logging, we will write out the data supplied to the users * home folder under .a2opt/a2opt-MMDDYY.log * We remove any old log files * */ public function log_action($data) { /* $this->home should be the user's home directory */ $log_enable = $this->home . '/.a2logenable'; $log_dir = $this->home . '/.a2opt'; // create folder for logs if not exist if (!file_exists($log_dir)) { mkdir($log_dir); } // check for old log files and remove, check if log flag is old as well if (file_exists($log_enable) && date('U', filemtime($log_enable)) < strtotime('-1 Day')) { unlink($log_enable); } // Delete log files older than a week $log_files = glob($log_dir . '/*'); foreach ($log_files as $file) { if (is_file($file) && date('U', filemtime($file)) < strtotime('-1 Week')) { unlink($file); } } if (file_exists($log_enable)) { // if support has flagged this account for logging, then we check if a file exists for today, create if not $log = $log_dir . '/a2opt-' . date('mdy') . '.log'; touch($log); // append supplied data to the log file $handle = fopen($log, 'a') or die('Cannot open file: ' . $log); fwrite($handle, date('Y-m-d H:i:s') . ':::' . $data . "\n"); } } } Save