| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| name: PHP Check | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - release-2.1 | ||
| pull_request: | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #4.2.2 | ||
|
|
||
| - name: Cache Composer packages | ||
| id: composer-cache | ||
| uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf #4.2.2 | ||
| with: | ||
| path: vendor | ||
| key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} | ||
| restore-keys: ${{ runner.os }}-php- | ||
|
|
||
| - name: Install dependencies | ||
| if: steps.composer-cache.outputs.cache-hit != 'true' | ||
| run: composer install --prefer-dist --no-progress --ansi | ||
|
|
||
| - run: php -v | ||
|
|
||
| - name: Checking for sign off (GPG also accepted) | ||
| run: php ./vendor/simplemachines/build-tools/check-signed-off.php | ||
|
|
||
| - name: Checking file integrity | ||
| run: | | ||
| php ./vendor/simplemachines/build-tools/check-eof.php | ||
| php ./vendor/simplemachines/build-tools/check-smf-license.php | ||
| php ./vendor/simplemachines/build-tools/check-smf-languages.php | ||
| php ./vendor/simplemachines/build-tools/check-version.php | ||
| lint: | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| matrix: | ||
| php: [ 7.0, 7.1, 7.2, 7.3, 7.4, 8.0, 8.1 ] | ||
|
|
||
| name: PHP ${{ matrix.php }} Syntax Check | ||
| steps: | ||
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #4.2.2 | ||
|
|
||
| - name: Setup PHP ${{ matrix.php }} | ||
| uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 #2.32.0 | ||
| with: | ||
| php-version: ${{ matrix.php }} | ||
| coverage: none | ||
|
|
||
| - name: Cache Composer packages | ||
| id: composer-cache | ||
| uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf #4.2.2 | ||
| with: | ||
| path: vendor | ||
| key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} | ||
| restore-keys: ${{ runner.os }}-php- | ||
|
|
||
| - name: Install dependencies | ||
| if: steps.composer-cache.outputs.cache-hit != 'true' | ||
| run: composer install --prefer-dist --no-progress --ansi | ||
|
|
||
| - name: Lint PHP files | ||
| run: vendor/bin/phplint -w --exclude .git --exclude vendor --ansi . |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| name: Update Year | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
|
|
||
| jobs: | ||
| update-year: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #4.2.2 | ||
|
|
||
| - name: Set current year environment variable | ||
| run: echo "CURRENT_YEAR=$(date +'%Y')" >> $GITHUB_ENV | ||
|
|
||
| - name: Update year in PHP files | ||
| run: | | ||
| for file in SSI.php proxy.php cron.php index.php other/*.php; do | ||
| sed -i "s/@copyright [0-9]\{4\}/@copyright ${{ env.CURRENT_YEAR }}/" $file | ||
| sed -i "s/define('SMF_SOFTWARE_YEAR', '[0-9]\{4\}');/define('SMF_SOFTWARE_YEAR', '${{ env.CURRENT_YEAR }}');/" $file | ||
| done | ||
| - name: Create Pull Request | ||
| uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e #7.0.8 | ||
| with: | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| commit-message: Update year to ${{ env.CURRENT_YEAR }} | ||
| branch: 2.1/update-year-${{ env.CURRENT_YEAR }} | ||
| title: "[2.1] Update year to ${{ env.CURRENT_YEAR }}" | ||
| body: This PR updates the year to ${{ env.CURRENT_YEAR }}. This action was performed by a bot. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * This file handles the user and privacy policy agreements. | ||
| * | ||
| * Simple Machines Forum (SMF) | ||
| * | ||
| * @package SMF | ||
| * @author Simple Machines https://www.simplemachines.org | ||
| * @copyright 2022 Simple Machines and individual contributors | ||
| * @license https://www.simplemachines.org/about/smf/license.php BSD | ||
| * | ||
| * @version 2.1.0 | ||
| */ | ||
|
|
||
| if (!defined('SMF')) | ||
| die('No direct access...'); | ||
|
|
||
| /* The purpose of this file is to show the user an updated registration | ||
| agreement, and get them to agree to it. | ||
| bool prepareAgreementContext() | ||
| // !!! | ||
| bool canRequireAgreement() | ||
| // !!! | ||
| bool canRequirePrivacyPolicy() | ||
| // !!! | ||
| void Agreement() | ||
| - Show the new registration agreement | ||
| void AcceptAgreement() | ||
| - Called when they actually accept the agreement | ||
| - Save the date of the current agreement to the members database table | ||
| - Redirect back to wherever they came from | ||
| */ | ||
|
|
||
| function prepareAgreementContext() | ||
| { | ||
| global $boarddir, $context, $language, $modSettings, $user_info; | ||
|
|
||
| // What, if anything, do they need to accept? | ||
| $context['can_accept_agreement'] = !empty($modSettings['requireAgreement']) && canRequireAgreement(); | ||
| $context['can_accept_privacy_policy'] = !empty($modSettings['requirePolicyAgreement']) && canRequirePrivacyPolicy(); | ||
| $context['accept_doc'] = $context['can_accept_agreement'] || $context['can_accept_privacy_policy']; | ||
|
|
||
| if (!$context['accept_doc'] || $context['can_accept_agreement']) | ||
| { | ||
| // Grab the agreement. | ||
| // Have we got a localized one? | ||
| if (file_exists($boarddir . '/agreement.' . $user_info['language'] . '.txt')) | ||
| $context['agreement_file'] = $boarddir . '/agreement.' . $user_info['language'] . '.txt'; | ||
| elseif (file_exists($boarddir . '/agreement.txt')) | ||
| $context['agreement_file'] = $boarddir . '/agreement.txt'; | ||
|
|
||
| if (!empty($context['agreement_file'])) | ||
| { | ||
| $cache_id = strtr($context['agreement_file'], array($boarddir => '', '.txt' => '', '.' => '_')); | ||
| $context['agreement'] = parse_bbc(file_get_contents($context['agreement_file']), true, $cache_id); | ||
| } | ||
| elseif ($context['can_accept_agreement']) | ||
| fatal_lang_error('error_no_agreement', false); | ||
| } | ||
|
|
||
| if (!$context['accept_doc'] || $context['can_accept_privacy_policy']) | ||
| { | ||
| // Have we got a localized policy? | ||
| if (!empty($modSettings['policy_' . $user_info['language']])) | ||
| $context['privacy_policy'] = parse_bbc($modSettings['policy_' . $user_info['language']]); | ||
| elseif (!empty($modSettings['policy_' . $language])) | ||
| $context['privacy_policy'] = parse_bbc($modSettings['policy_' . $language]); | ||
| // Then I guess we've got nothing | ||
| elseif ($context['can_accept_privacy_policy']) | ||
| fatal_lang_error('error_no_privacy_policy', false); | ||
| } | ||
| } | ||
|
|
||
| function canRequireAgreement() | ||
| { | ||
| global $boarddir, $context, $modSettings, $options, $user_info; | ||
|
|
||
| // Guests can't agree | ||
| if (!empty($user_info['is_guest']) || empty($modSettings['requireAgreement'])) | ||
| return false; | ||
|
|
||
| $agreement_lang = file_exists($boarddir . '/agreement.' . $user_info['language'] . '.txt') ? $user_info['language'] : 'default'; | ||
|
|
||
| if (empty($modSettings['agreement_updated_' . $agreement_lang])) | ||
| return false; | ||
|
|
||
| $context['agreement_accepted_date'] = empty($options['agreement_accepted']) ? 0 : $options['agreement_accepted']; | ||
|
|
||
| // A new timestamp means that there are new changes to the registration agreement and must therefore be shown. | ||
| return empty($options['agreement_accepted']) || $modSettings['agreement_updated_' . $agreement_lang] > $options['agreement_accepted']; | ||
| } | ||
|
|
||
| function canRequirePrivacyPolicy() | ||
| { | ||
| global $modSettings, $options, $user_info, $language, $context; | ||
|
|
||
| if (!empty($user_info['is_guest']) || empty($modSettings['requirePolicyAgreement'])) | ||
| return false; | ||
|
|
||
| $policy_lang = !empty($modSettings['policy_' . $user_info['language']]) ? $user_info['language'] : $language; | ||
|
|
||
| if (empty($modSettings['policy_updated_' . $policy_lang])) | ||
| return false; | ||
|
|
||
| $context['privacy_policy_accepted_date'] = empty($options['policy_accepted']) ? 0 : $options['policy_accepted']; | ||
|
|
||
| return empty($options['policy_accepted']) || $modSettings['policy_updated_' . $policy_lang] > $options['policy_accepted']; | ||
| } | ||
|
|
||
| // Let's tell them there's a new agreement | ||
| function Agreement() | ||
| { | ||
| global $context, $modSettings, $scripturl, $smcFunc, $txt; | ||
|
|
||
| prepareAgreementContext(); | ||
|
|
||
| loadLanguage('Agreement'); | ||
| loadTemplate('Agreement'); | ||
|
|
||
| $page_title = ''; | ||
| if (!empty($context['agreement']) && !empty($context['privacy_policy'])) | ||
| $page_title = $txt['agreement_and_privacy_policy']; | ||
| elseif (!empty($context['agreement'])) | ||
| $page_title = $txt['agreement']; | ||
| elseif (!empty($context['privacy_policy'])) | ||
| $page_title = $txt['privacy_policy']; | ||
|
|
||
| $context['page_title'] = $page_title; | ||
| $context['linktree'][] = array( | ||
| 'url' => $scripturl . '?action=agreement', | ||
| 'name' => $context['page_title'], | ||
| ); | ||
|
|
||
| if (isset($_SESSION['old_url'])) | ||
| $_SESSION['redirect_url'] = $_SESSION['old_url']; | ||
|
|
||
| } | ||
|
|
||
| // I solemly swear to no longer chase squirrels. | ||
| function AcceptAgreement() | ||
| { | ||
| global $context, $modSettings, $smcFunc, $user_info; | ||
|
|
||
| $can_accept_agreement = !empty($modSettings['requireAgreement']) && canRequireAgreement(); | ||
| $can_accept_privacy_policy = !empty($modSettings['requirePolicyAgreement']) && canRequirePrivacyPolicy(); | ||
|
|
||
| if ($can_accept_agreement || $can_accept_privacy_policy) | ||
| { | ||
| checkSession(); | ||
|
|
||
| if ($can_accept_agreement) | ||
| { | ||
| $smcFunc['db_insert']('replace', | ||
| '{db_prefix}themes', | ||
| array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'), | ||
| array($user_info['id'], 1, 'agreement_accepted', time()), | ||
| array('id_member', 'id_theme', 'variable') | ||
| ); | ||
| logAction('agreement_accepted', array('applicator' => $user_info['id']), 'user'); | ||
| } | ||
|
|
||
| if ($can_accept_privacy_policy) | ||
| { | ||
| $smcFunc['db_insert']('replace', | ||
| '{db_prefix}themes', | ||
| array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'), | ||
| array($user_info['id'], 1, 'policy_accepted', time()), | ||
| array('id_member', 'id_theme', 'variable') | ||
| ); | ||
| logAction('policy_accepted', array('applicator' => $user_info['id']), 'user'); | ||
| } | ||
| } | ||
|
|
||
| // Redirect back to chasing those squirrels, er, viewing those memes. | ||
| redirectexit(!empty($_SESSION['redirect_url']) ? $_SESSION['redirect_url'] : ''); | ||
| } | ||
|
|
||
| ?> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,213 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * Simple Machines Forum (SMF) | ||
| * | ||
| * @package SMF | ||
| * @author Simple Machines https://www.simplemachines.org | ||
| * @copyright 2022 Simple Machines and individual contributors | ||
| * @license https://www.simplemachines.org/about/smf/license.php BSD | ||
| * | ||
| * @version 2.1.2 | ||
| */ | ||
|
|
||
| namespace SMF\Cache\APIs; | ||
|
|
||
| use Memcached; | ||
| use SMF\Cache\CacheApi; | ||
| use SMF\Cache\CacheApiInterface; | ||
|
|
||
| if (!defined('SMF')) | ||
| die('No direct access...'); | ||
|
|
||
| /** | ||
| * Our Cache API class | ||
| * | ||
| * @package CacheAPI | ||
| */ | ||
| class MemcachedImplementation extends CacheApi implements CacheApiInterface | ||
| { | ||
| const CLASS_KEY = 'cache_memcached'; | ||
|
|
||
| /** @var Memcached The memcache instance. */ | ||
| private $memcached = null; | ||
|
|
||
| /** @var string[] */ | ||
| private $servers; | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function __construct() | ||
| { | ||
| global $cache_memcached; | ||
|
|
||
| $this->servers = array_map( | ||
| function($server) | ||
| { | ||
| if (strpos($server, '/') !== false) | ||
| return array($server, 0); | ||
|
|
||
| else | ||
| { | ||
| $server = explode(':', $server); | ||
| return array($server[0], isset($server[1]) ? (int) $server[1] : 11211); | ||
| } | ||
| }, | ||
| explode(',', $cache_memcached) | ||
| ); | ||
|
|
||
| parent::__construct(); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function isSupported($test = false) | ||
| { | ||
| global $cache_memcached; | ||
|
|
||
| $supported = class_exists('Memcached'); | ||
|
|
||
| if ($test) | ||
| return $supported; | ||
|
|
||
| return parent::isSupported() && $supported && !empty($cache_memcached); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function connect() | ||
| { | ||
| $this->memcached = new Memcached; | ||
|
|
||
| return $this->addServers(); | ||
| } | ||
|
|
||
| /** | ||
| * Add memcached servers. | ||
| * | ||
| * Don't add servers if they already exist. Ideal for persistent connections. | ||
| * | ||
| * @return bool True if there are servers in the daemon, false if not. | ||
| */ | ||
| protected function addServers() | ||
| { | ||
| $currentServers = $this->memcached->getServerList(); | ||
| $retVal = !empty($currentServers); | ||
| foreach ($this->servers as $server) | ||
| { | ||
| // Figure out if we have this server or not | ||
| $foundServer = false; | ||
| foreach ($currentServers as $currentServer) | ||
| { | ||
| if ($server[0] == $currentServer['host'] && $server[1] == $currentServer['port']) | ||
| { | ||
| $foundServer = true; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| // Found it? | ||
| if (empty($foundServer)) | ||
| $retVal |= $this->memcached->addServer($server[0], $server[1]); | ||
| } | ||
|
|
||
| return $retVal; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function getData($key, $ttl = null) | ||
| { | ||
| $key = $this->prefix . strtr($key, ':/', '-_'); | ||
|
|
||
| $value = $this->memcached->get($key); | ||
|
|
||
| // $value should return either data or false (from failure, key not found or empty array). | ||
| if ($value === false) | ||
| return null; | ||
|
|
||
| return $value; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function putData($key, $value, $ttl = null) | ||
| { | ||
| $key = $this->prefix . strtr($key, ':/', '-_'); | ||
|
|
||
| return $this->memcached->set($key, $value, $ttl !== null ? $ttl : $this->ttl); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function cleanCache($type = '') | ||
| { | ||
| $this->invalidateCache(); | ||
|
|
||
| // Memcached accepts a delay parameter, always use 0 (instant). | ||
| return $this->memcached->flush(0); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function quit() | ||
| { | ||
| return $this->memcached->quit(); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function cacheSettings(array &$config_vars) | ||
| { | ||
| global $context, $txt; | ||
|
|
||
| if (!in_array($txt[self::CLASS_KEY .'_settings'], $config_vars)) | ||
| { | ||
| $config_vars[] = $txt[self::CLASS_KEY .'_settings']; | ||
| $config_vars[] = array( | ||
| self::CLASS_KEY, | ||
| $txt[self::CLASS_KEY .'_servers'], | ||
| 'file', | ||
| 'text', | ||
| 0, | ||
| 'subtext' => $txt[self::CLASS_KEY .'_servers_subtext']); | ||
| } | ||
|
|
||
| if (!isset($context['settings_post_javascript'])) | ||
| $context['settings_post_javascript'] = ''; | ||
|
|
||
| if (empty($context['settings_not_writable'])) | ||
| $context['settings_post_javascript'] .= ' | ||
| $("#cache_accelerator").change(function (e) { | ||
| var cache_type = e.currentTarget.value; | ||
| $("#'. self::CLASS_KEY .'").prop("disabled", cache_type != "MemcacheImplementation" && cache_type != "MemcachedImplementation"); | ||
| });'; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function getVersion() | ||
| { | ||
| if (!is_object($this->memcached)) | ||
| return false; | ||
|
|
||
| // This gets called in Subs-Admin getServerVersions when loading up support information. If we can't get a connection, return nothing. | ||
| $result = $this->memcached->getVersion(); | ||
|
|
||
| if (!empty($result)) | ||
| return current($result); | ||
|
|
||
| return false; | ||
| } | ||
| } | ||
|
|
||
| ?> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * Simple Machines Forum (SMF) | ||
| * | ||
| * @package SMF | ||
| * @author Simple Machines https://www.simplemachines.org | ||
| * @copyright 2022 Simple Machines and individual contributors | ||
| * @license https://www.simplemachines.org/about/smf/license.php BSD | ||
| * | ||
| * @version 2.1.0 | ||
| */ | ||
|
|
||
| namespace SMF\Cache\APIs; | ||
|
|
||
| use SMF\Cache\CacheApi; | ||
| use SMF\Cache\CacheApiInterface; | ||
|
|
||
| if (!defined('SMF')) | ||
| die('No direct access...'); | ||
|
|
||
| /** | ||
| * PostgreSQL Cache API class | ||
| * | ||
| * @package CacheAPI | ||
| */ | ||
| class Postgres extends CacheApi implements CacheApiInterface | ||
| { | ||
| /** @var string */ | ||
| private $db_prefix; | ||
|
|
||
| /** @var resource result of pg_connect. */ | ||
| private $db_connection; | ||
|
|
||
| public function __construct() | ||
| { | ||
| global $db_prefix, $db_connection; | ||
|
|
||
| $this->db_prefix = $db_prefix; | ||
| $this->db_connection = $db_connection; | ||
|
|
||
| parent::__construct(); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function connect() | ||
| { | ||
| $result = pg_query_params($this->db_connection, 'SELECT 1 | ||
| FROM pg_tables | ||
| WHERE schemaname = $1 | ||
| AND tablename = $2', | ||
| array( | ||
| 'public', | ||
| $this->db_prefix . 'cache', | ||
| ) | ||
| ); | ||
|
|
||
| if (pg_affected_rows($result) === 0) | ||
| pg_query($this->db_connection, 'CREATE UNLOGGED TABLE ' . $this->db_prefix . 'cache (key text, value text, ttl bigint, PRIMARY KEY (key))'); | ||
|
|
||
| $this->prepareQueries( | ||
| array( | ||
| 'smf_cache_get_data', | ||
| 'smf_cache_put_data', | ||
| 'smf_cache_delete_data', | ||
| ), | ||
| array( | ||
| 'SELECT value FROM ' . $this->db_prefix . 'cache WHERE key = $1 AND ttl >= $2 LIMIT 1', | ||
| 'INSERT INTO ' . $this->db_prefix . 'cache(key,value,ttl) VALUES($1,$2,$3) | ||
| ON CONFLICT(key) DO UPDATE SET value = $2, ttl = $3', | ||
| 'DELETE FROM ' . $this->db_prefix . 'cache WHERE key = $1', | ||
| ) | ||
| ); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Stores a prepared SQL statement, ensuring that it's not done twice. | ||
| * | ||
| * @param array $stmtnames | ||
| * @param array $queries | ||
| */ | ||
| private function prepareQueries(array $stmtnames, array $queries) | ||
| { | ||
| $result = pg_query_params( | ||
| $this->db_connection, | ||
| 'SELECT name FROM pg_prepared_statements WHERE name = ANY ($1)', | ||
| array('{' . implode(', ', $stmtnames) . '}') | ||
| ); | ||
|
|
||
| $arr = pg_num_rows($result) == 0 ? array() : array_map( | ||
| function($el) | ||
| { | ||
| return $el['name']; | ||
| }, | ||
| pg_fetch_all($result) | ||
| ); | ||
| foreach ($stmtnames as $idx => $stmtname) | ||
| if (!in_array($stmtname, $arr)) | ||
| pg_prepare($this->db_connection, $stmtname, $queries[$idx]); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function isSupported($test = false) | ||
| { | ||
| global $smcFunc; | ||
|
|
||
| if ($smcFunc['db_title'] !== POSTGRE_TITLE) | ||
| return false; | ||
|
|
||
| $result = pg_query($this->db_connection, 'SHOW server_version_num'); | ||
| $res = pg_fetch_assoc($result); | ||
|
|
||
| if ($res['server_version_num'] < 90500) | ||
| return false; | ||
|
|
||
| return $test ? true : parent::isSupported(); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function getData($key, $ttl = null) | ||
| { | ||
| $result = pg_execute($this->db_connection, 'smf_cache_get_data', array($key, time())); | ||
|
|
||
| if (pg_affected_rows($result) === 0) | ||
| return null; | ||
|
|
||
| $res = pg_fetch_assoc($result); | ||
|
|
||
| return $res['value']; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function putData($key, $value, $ttl = null) | ||
| { | ||
| $ttl = time() + (int) ($ttl !== null ? $ttl : $this->ttl); | ||
|
|
||
| if ($value === null) | ||
| $result = pg_execute($this->db_connection, 'smf_cache_delete_data', array($key)); | ||
| else | ||
| $result = pg_execute($this->db_connection, 'smf_cache_put_data', array($key, $value, $ttl)); | ||
|
|
||
| return pg_affected_rows($result) > 0; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function cleanCache($type = '') | ||
| { | ||
| if ($type == 'expired') | ||
| pg_query($this->db_connection, 'DELETE FROM ' . $this->db_prefix . 'cache WHERE ttl < ' . time() . ';'); | ||
| else | ||
| pg_query($this->db_connection, 'TRUNCATE ' . $this->db_prefix . 'cache'); | ||
|
|
||
| $this->invalidateCache(); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function getVersion() | ||
| { | ||
| return pg_version($this->db_connection)['server']; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| public function housekeeping() | ||
| { | ||
| $this->createTempTable(); | ||
| $this->cleanCache(); | ||
| $this->retrieveData(); | ||
| $this->deleteTempTable(); | ||
| } | ||
|
|
||
| /** | ||
| * Create the temp table of valid data. | ||
| * | ||
| * @return void | ||
| */ | ||
| private function createTempTable() | ||
| { | ||
| pg_query($this->db_connection, 'CREATE LOCAL TEMP TABLE IF NOT EXISTS ' . $this->db_prefix . 'cache_tmp AS SELECT * FROM ' . $this->db_prefix . 'cache WHERE ttl >= ' . time()); | ||
| } | ||
|
|
||
| /** | ||
| * Delete the temp table. | ||
| * | ||
| * @return void | ||
| */ | ||
| private function deleteTempTable() | ||
| { | ||
| pg_query($this->db_connection, 'DROP TABLE IF EXISTS ' . $this->db_prefix . 'cache_tmp'); | ||
| } | ||
|
|
||
| /** | ||
| * Retrieve the valid data from temp table. | ||
| * | ||
| * @return void | ||
| */ | ||
| private function retrieveData() | ||
| { | ||
| pg_query($this->db_connection, 'INSERT INTO ' . $this->db_prefix . 'cache SELECT * FROM ' . $this->db_prefix . 'cache_tmp ON CONFLICT DO NOTHING'); | ||
| } | ||
| } | ||
|
|
||
| ?> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,252 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * Simple Machines Forum (SMF) | ||
| * | ||
| * @package SMF | ||
| * @author Simple Machines https://www.simplemachines.org | ||
| * @copyright 2022 Simple Machines and individual contributors | ||
| * @license https://www.simplemachines.org/about/smf/license.php BSD | ||
| * | ||
| * @version 2.1.0 | ||
| */ | ||
|
|
||
| namespace SMF\Cache; | ||
|
|
||
| if (!defined('SMF')) | ||
| die('No direct access...'); | ||
|
|
||
| abstract class CacheApi | ||
| { | ||
| const APIS_FOLDER = 'APIs'; | ||
| const APIS_NAMESPACE = 'SMF\Cache\APIs\\'; | ||
| const APIS_DEFAULT = 'FileBased'; | ||
|
|
||
| /** | ||
| * @var string The maximum SMF version that this will work with. | ||
| */ | ||
| protected $version_compatible = '2.1.999'; | ||
|
|
||
| /** | ||
| * @var string The minimum SMF version that this will work with. | ||
| */ | ||
| protected $min_smf_version = '2.1 RC1'; | ||
|
|
||
| /** | ||
| * @var string The prefix for all keys. | ||
| */ | ||
| protected $prefix = ''; | ||
|
|
||
| /** | ||
| * @var int The default TTL. | ||
| */ | ||
| protected $ttl = 120; | ||
|
|
||
| /** | ||
| * Does basic setup of a cache method when we create the object but before we call connect. | ||
| * | ||
| * @access public | ||
| */ | ||
| public function __construct() | ||
| { | ||
| $this->setPrefix(); | ||
| } | ||
|
|
||
| /** | ||
| * Checks whether we can use the cache method performed by this API. | ||
| * | ||
| * @access public | ||
| * @param bool $test Test if this is supported or enabled. | ||
| * @return bool Whether or not the cache is supported | ||
| */ | ||
| public function isSupported($test = false) | ||
| { | ||
| global $cache_enable; | ||
|
|
||
| if ($test) | ||
| return true; | ||
|
|
||
| return !empty($cache_enable); | ||
| } | ||
|
|
||
| /** | ||
| * Sets the cache prefix. | ||
| * | ||
| * @access public | ||
| * @param string $prefix The prefix to use. | ||
| * If empty, the prefix will be generated automatically. | ||
| * @return bool If this was successful or not. | ||
| */ | ||
| public function setPrefix($prefix = '') | ||
| { | ||
| global $boardurl, $cachedir, $boarddir; | ||
|
|
||
| if (!is_string($prefix)) | ||
| $prefix = ''; | ||
|
|
||
| // Use the supplied prefix, if there is one. | ||
| if (!empty($prefix)) | ||
| { | ||
| $this->prefix = $prefix; | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| // Ideally the prefix should reflect the last time the cache was reset. | ||
| if (!empty($cachedir) && file_exists($cachedir . '/index.php')) | ||
| { | ||
| $mtime = filemtime($cachedir . '/index.php'); | ||
| } | ||
| // Fall back to the last time that Settings.php was updated. | ||
| elseif (!empty($boarddir) && file_exists($boarddir . '/Settings.php')) | ||
| { | ||
| $mtime = filemtime($boarddir . '/Settings.php'); | ||
| } | ||
| // This should never happen, but just in case... | ||
| else | ||
| { | ||
| $mtime = filemtime(realpath($_SERVER['SCRIPT_FILENAME'])); | ||
| } | ||
|
|
||
| $this->prefix = md5($boardurl . $mtime) . '-SMF-'; | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the prefix as defined from set or the default. | ||
| * | ||
| * @access public | ||
| * @return string the value of $key. | ||
| */ | ||
| public function getPrefix() | ||
| { | ||
| return $this->prefix; | ||
| } | ||
|
|
||
| /** | ||
| * Sets a default Time To Live, if this isn't specified we let the class define it. | ||
| * | ||
| * @access public | ||
| * @param int $ttl The default TTL | ||
| * @return bool If this was successful or not. | ||
| */ | ||
| public function setDefaultTTL($ttl = 120) | ||
| { | ||
| $this->ttl = $ttl; | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the TTL as defined from set or the default. | ||
| * | ||
| * @access public | ||
| * @return int the value of $ttl. | ||
| */ | ||
| public function getDefaultTTL() | ||
| { | ||
| return $this->ttl; | ||
| } | ||
|
|
||
| /** | ||
| * Invalidate all cached data. | ||
| * | ||
| * @return bool Whether or not we could invalidate the cache. | ||
| */ | ||
| public function invalidateCache() | ||
| { | ||
| global $cachedir; | ||
|
|
||
| // Invalidate cache, to be sure! | ||
| // ... as long as index.php can be modified, anyway. | ||
| if (is_writable($cachedir . '/' . 'index.php')) | ||
| @touch($cachedir . '/' . 'index.php'); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Closes connections to the cache method. | ||
| * | ||
| * @access public | ||
| * @return bool Whether the connections were closed. | ||
| */ | ||
| public function quit() | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Specify custom settings that the cache API supports. | ||
| * | ||
| * @access public | ||
| * @param array $config_vars Additional config_vars, see ManageSettings.php for usage. | ||
| */ | ||
| public function cacheSettings(array &$config_vars) | ||
| { | ||
| } | ||
|
|
||
| /** | ||
| * Gets the latest version of SMF this is compatible with. | ||
| * | ||
| * @access public | ||
| * @return string the value of $key. | ||
| */ | ||
| public function getCompatibleVersion() | ||
| { | ||
| return $this->version_compatible; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the min version that we support. | ||
| * | ||
| * @access public | ||
| * @return string the value of $key. | ||
| */ | ||
| public function getMinimumVersion() | ||
| { | ||
| return $this->min_smf_version; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the Version of the Caching API. | ||
| * | ||
| * @access public | ||
| * @return string the value of $key. | ||
| */ | ||
| public function getVersion() | ||
| { | ||
| return $this->min_smf_version; | ||
| } | ||
|
|
||
| /** | ||
| * Run housekeeping of this cache | ||
| * exp. clean up old data or do optimization | ||
| * | ||
| * @access public | ||
| * @return void | ||
| */ | ||
| public function housekeeping() | ||
| { | ||
| } | ||
|
|
||
| /** | ||
| * Gets the class identifier of the current caching API implementation. | ||
| * | ||
| * @access public | ||
| * @return string the unique identifier for the current class implementation. | ||
| */ | ||
| public function getImplementationClassKeyName() | ||
| { | ||
| $class_name = get_class($this); | ||
|
|
||
| if ($position = strrpos($class_name, '\\')) | ||
| return substr($class_name, $position + 1); | ||
|
|
||
| else | ||
| return get_class($this); | ||
| } | ||
| } | ||
|
|
||
| ?> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * Simple Machines Forum (SMF) | ||
| * | ||
| * @package SMF | ||
| * @author Simple Machines https://www.simplemachines.org | ||
| * @copyright 2022 Simple Machines and individual contributors | ||
| * @license https://www.simplemachines.org/about/smf/license.php BSD | ||
| * | ||
| * @version 2.1.0 | ||
| */ | ||
|
|
||
| namespace SMF\Cache; | ||
|
|
||
| if (!defined('SMF')) | ||
| die('No direct access...'); | ||
|
|
||
|
|
||
| interface CacheApiInterface | ||
| { | ||
| /** | ||
| * Checks whether we can use the cache method performed by this API. | ||
| * | ||
| * @access public | ||
| * @param bool $test Test if this is supported or enabled. | ||
| * @return bool Whether or not the cache is supported | ||
| */ | ||
| public function isSupported($test = false); | ||
|
|
||
| /** | ||
| * Connects to the cache method. This defines our $key. If this fails, we return false, otherwise we return true. | ||
| * | ||
| * @access public | ||
| * @return bool Whether or not the cache method was connected to. | ||
| */ | ||
| public function connect(); | ||
|
|
||
| /** | ||
| * Retrieves an item from the cache. | ||
| * | ||
| * @access public | ||
| * @param string $key The key to use, the prefix is applied to the key name. | ||
| * @param int $ttl Overrides the default TTL. Not really used anymore, | ||
| * but is kept for backwards compatibility. | ||
| * @return mixed The result from the cache, if there is no data or it is invalid, we return null. | ||
| * @todo Seperate existence checking into its own method | ||
| */ | ||
| public function getData($key, $ttl = null); | ||
|
|
||
| /** | ||
| * Stores a value, regardless of whether or not the key already exists (in | ||
| * which case it will overwrite the existing value for that key). | ||
| * | ||
| * @access public | ||
| * @param string $key The key to use, the prefix is applied to the key name. | ||
| * @param mixed $value The data we wish to save. Use null to delete. | ||
| * @param int $ttl How long (in seconds) the data should be cached for. | ||
| * The default TTL will be used if this is null. | ||
| * @return bool Whether or not we could save this to the cache. | ||
| * @todo Seperate deletion into its own method | ||
| */ | ||
| public function putData($key, $value, $ttl = null); | ||
|
|
||
| /** | ||
| * Clean out the cache. | ||
| * | ||
| * @param string $type If supported, the type of cache to clear, blank/data or user. | ||
| * @return bool Whether or not we could clean the cache. | ||
| */ | ||
| public function cleanCache($type = ''); | ||
|
|
||
| /** | ||
| * Gets the class identifier of the current caching API implementation. | ||
| * | ||
| * @access public | ||
| * @return string the unique identifier for the current class implementation. | ||
| */ | ||
| public function getImplementationClassKeyName(); | ||
| } | ||
|
|
||
| ?> |