public function __construct($ip_addr) { try { if ($ip_addr == '127.0.0.1') { // Load local connections up with Columbus, OH. // Why? ;) $cache = ['city' => 'Columbus', 'province' => 'OH', 'country' => 'US', 'timezone' => 'America/New_York', 'postal' => '43215']; } else { $cacheKey = 'iplookup-' . $ip_addr; $cache = Cache::Get($cacheKey); if (!$cache) { $reader = new \GeoIp2\Database\Reader(ROOT_PDIR . 'components/geographic-codes/libs/maxmind-geolite-db/GeoLite2-City.mmdb'); /** @var \GeoIp2\Model\CityIspOrg $geo */ $geo = $reader->cityIspOrg($ip_addr); //$geo = $reader->cityIspOrg('67.149.214.236'); $reader->close(); $sd = isset($geo->subdivisions[0]) ? $geo->subdivisions[0] : null; $cache = ['city' => $geo->city->name, 'province' => $sd ? $sd->isoCode : '', 'country' => $geo->country->isoCode, 'timezone' => $geo->location->timeZone, 'postal' => $geo->postal->code]; Cache::Set($cacheKey, $cache, SECONDS_ONE_WEEK); } } } catch (\Exception $e) { // Well, we tried! Load something at least. $cacheKey = 'iplookup-' . $ip_addr; $cache = ['city' => 'McMurdo Base', 'province' => '', 'country' => 'AQ', 'timezone' => 'CAST', 'postal' => '']; Cache::Set($cacheKey, $cache, SECONDS_ONE_HOUR); } $this->city = $cache['city']; $this->province = $cache['province']; $this->country = $cache['country']; $this->timezone = $cache['timezone']; $this->postal = $cache['postal']; }
private function _registerFeature($feature, $url, $component){ if(!defined('SERVER_ID')){ throw new \Exception('Unable to load the licenser without a valid server id set!'); } // Lookup any matching feature and do not allow two components to register the same feature code! if(isset($this->_features[$feature]) && $this->_features[$feature]['component'] != $component){ throw new \Exception('Feature ' . $feature . ' already defined in another component! (' . $this->_features[$feature]['component'] . ' vs ' . $component . ')'); } // Lookup the cache for this licensed key value. $cached = \Core\Cache::Get(md5('LICENSER:' . SERVER_ID . $feature)); $this->_features[$feature] = [ 'feature' => $feature, 'url' => $url, 'component' => $component, 'value' => $cached, ]; }
/** * Lookup query * * @param string $query IP or hostname to lookup * @param bool $is_utf Require UTF-8 * * @return WhoisResult */ public static function Lookup($query = '', $is_utf = true) { // See if this query has been cached by Core <3 $cachekey = \Core\str_to_url('whois-' . $query); $cached = \Core\Cache::Get($cachekey); if ($cached) { $result = $cached; } else { $whois = new phpwhois\Whois(); $result = $whois->lookup($query, $is_utf); // Cache the results for 6 hours \Core\Cache::Set($cachekey, $result, 3600 * 6); } if (!is_array($result)) { return new WhoisNotFoundResult($query); } if (!sizeof($result)) { return new WhoisNotFoundResult($query); } return new WhoisResult($result, $query); }
/** * Get the temporary local version of the file. * This is useful for doing operations such as hash and identicalto. * * @return FileLocal */ protected function _getTmpLocal() { if ($this->_tmplocal === null) { $f = md5($this->getFilename()); // Gotta love obviously-named flags. $needtodownload = true; $this->_tmplocal = Filestore\Factory::File('tmp/remotefile-cache/' . $f); // File exists already! Check and see if it needs to be redownloaded. if ($this->cacheable && $this->_tmplocal->exists()) { // Lookup this file in the system cache. $systemcachedata = Cache::Get('remotefile-cache-header-' . $f); if ($systemcachedata && isset($systemcachedata['headers'])) { // I can only look them up if the cache is available. // First check will be the expires header. if(isset($systemcachedata['headers']['Expires']) && strtotime($systemcachedata['headers']['Expires']) > time()){ $needtodownload = false; // And set the headers! // This is required $this->_headers = $systemcachedata['headers']; $this->_response = $systemcachedata['response']; } // Next, try ETag. elseif ($this->_getHeader('ETag') && isset($systemcachedata['headers']['ETag'])) { $needtodownload = ($this->_getHeader('ETag') != $systemcachedata['headers']['ETag']); } // No? How 'bout elseif ($this->_getHeader('Last-Modified') && isset($systemcachedata['headers']['Last-Modified'])) { $needtodownload = ($this->_getHeader('Last-Modified') != $systemcachedata['headers']['Last-Modified']); } // Still no? The default is to download it anyway. } } if ($needtodownload || !$this->cacheable) { // Make sure that the headers are updated, this is a requirement to use the 302 tag. $this->_getHeaders(); if(($this->_response == '302' || $this->_response == '301') && $this->_redirectFile !== null){ $this->_tmplocal = $this->_redirectFile->_getTmpLocal(); } else{ // BTW, use cURL. $curl = curl_init(); curl_setopt_array( $curl, array( CURLOPT_HEADER => false, CURLOPT_NOBODY => false, CURLOPT_RETURNTRANSFER => true, CURLOPT_URL => $this->getURL(), CURLOPT_HTTPHEADER => \Core::GetStandardHTTPHeaders(true), ) ); $result = curl_exec($curl); if($result === false){ switch(curl_errno($curl)){ case CURLE_COULDNT_CONNECT: case CURLE_COULDNT_RESOLVE_HOST: case CURLE_COULDNT_RESOLVE_PROXY: $this->_response = 404; return $this->_tmplocal; break; default: $this->_response = 500; return $this->_tmplocal; break; } } curl_close($curl); // Copy the data down to the local file. $this->_tmplocal->putContents($result); } // And remember this header data for nexttime. Cache::Set( 'remotefile-cache-header-' . $f, [ 'headers' => $this->_getHeaders(), 'response' => $this->_response, ] ); } } return $this->_tmplocal; }
/** * Get an array of locales currently available on the base system. * * @return array */ public static function GetLocalesAvailable(){ $cacheKey = 'core-i18n-locales'; $cacheTime = DEVELOPMENT_MODE ? 3600 : 604800; $cached = Cache::Get($cacheKey, $cacheTime); if($cached){ return $cached; } exec('locale -a', $output); $locales = []; foreach($output as $line){ if($line == 'C' || $line == 'POSIX' || $line == 'C.UTF-8'){ // Yeah, skip these! continue; } if(($dotpos = strpos($line, '.')) !== false){ // Trim anything after the ".", this is the charset which we, (in theory), don't need. // ex: .UTF-8 or .UTF-16. $line = substr($line, 0, $dotpos); } if(isset(self::$Languages[$line])){ $locales[$line] = self::$Languages[$line]; } } // Cache this so I don't have to execute the command and lookup the values all over again! Cache::Set($cacheKey, $locales, $cacheTime); return $locales; }
/** * Construct a UserAgent object with full caching enabled. * * @param $useragent * * @return UserAgent */ public static function Construct($useragent = null){ if($useragent === null){ $useragent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; } $cachekey = 'useragent-constructor-' . md5($useragent); $cache = Cache::Get($cachekey); if(!$cache){ $cache = new UserAgent($useragent); Cache::Set($cachekey, $cache, SECONDS_ONE_WEEK); } return $cache; }
/** * Load all the components in the system, replacement for the Core. * @throws CoreException */ private function _loadComponents() { // cannot reload components. if ($this->_components) return null; $this->_components = array(); $this->_libraries = array(); $tempcomponents = false; Core\Utilities\Logger\write_debug('Starting loading of component metadata'); // If the site is in DEVELOPMENT mode, component caching would probably be a bad idea; ie: the developer probably wants // those component files loaded everytime. if(DEVELOPMENT_MODE){ $enablecache = false; } else{ $enablecache = true; } // Is there a cache of elements available? This is a primary system cache that greatly increases performance, // since it will no longer have to run through each component.xml file to register each one. if($enablecache){ Core\Utilities\Logger\write_debug('Checking core-components cache'); // Try to load up the cached components and check them first. $tempcomponents = \Core\Cache::Get('core-components', (3600 * 24)); if($tempcomponents !== false){ // Cached components only need to be loaded. foreach ($tempcomponents as $c) { try { $c->load(); } catch (Exception $e) { // Don't completely bail out here, just invalidate the cache and continue on. \Core\Cache::Delete('core-components'); $tempcomponents = false; } } } } if(!$enablecache || $tempcomponents == false){ \Core\Utilities\Profiler\Profiler::GetDefaultProfiler()->record('Scanning for component.xml files manually'); Core\Utilities\Logger\write_debug('Scanning for component.xml files manually'); // Core is first, (obviously) $tempcomponents['core'] = ComponentFactory::Load(ROOT_PDIR . 'core/component.xml'); Core\Utilities\Logger\write_debug('Core component loaded'); // First, build my cache of components, regardless if the component is installed or not. $dh = opendir(ROOT_PDIR . 'components'); if (!$dh) throw new CoreException('Unable to open directory [' . ROOT_PDIR . 'components/] for reading.'); // This will read through every directory in 'components', which is // where all components in the system are installed to. while (($file = readdir($dh)) !== false) { // skip hidden directories. if ($file{0} == '.') continue; // skip non-directories if (!is_dir(ROOT_PDIR . 'components/' . $file)) continue; // Skip directories that do not have a readable component.xml file. if (!is_readable(ROOT_PDIR . 'components/' . $file . '/component.xml')) continue; //Core\Utilities\Logger\write_debug(' * Loading component ' . $file); $c = ComponentFactory::Load(ROOT_PDIR . 'components/' . $file . '/component.xml'); Core\Utilities\Logger\write_debug('Opened component ' . $file); // All further operations are case insensitive. // The original call to Component needs to be case sensitive because it sets the filename to pull. $file = strtolower($file); // If the component was flagged as invalid.. just skip to the next one. if (!$c->isValid()) { if (DEVELOPMENT_MODE) { \Core\set_message('Component ' . $c->getName() . ' appears to be invalid.'); } continue; } $tempcomponents[$file] = $c; unset($c); } closedir($dh); \Core\Utilities\Profiler\Profiler::GetDefaultProfiler()->record('Component XML files scanned'); // Now I probably could actually load the components! foreach ($tempcomponents as $c) { /** @var Component_2_1 $c */ try { // Load some of the data in the class so that it's available in the cached version. // This is because the component 2.1 has built-in caching for many of the XML requests. // by calling them once, that lookup data is cached in that component, which in turn gets // copied to the cache version here! $c->load(); $c->getClassList(); $c->getViewSearchDir(); $c->getSmartyPluginDirectory(); $c->getWidgetList(); } catch (Exception $e) { var_dump($e); die(); } } // Cache this list! if($enablecache){ Core\Utilities\Logger\write_debug(' * Caching core-components for next pass'); \Core\Cache::Set('core-components', $tempcomponents, (3600 * 24)); } } $list = $tempcomponents; \Core\Utilities\Profiler\Profiler::GetDefaultProfiler()->record('Component metadata loaded, starting registration'); Core\Utilities\Logger\write_debug(' * Component metadata loaded, starting registration'); // The core component at a minimum needs to be loaded and registered. // $this->_registerComponent($list['core']); // $this->_components['core']->loadFiles(); // unset($list['core']); // Now that I have a list of components available, copy them into a list of // components that are installed. do { $size = sizeof($list); foreach ($list as $n => $c) { /** @var $c Component_2_1 */ // Disabled components don't get recognized. if($c->isInstalled() && !$c->isEnabled()){ // But they do get sent to the disabled list! $this->_componentsDisabled[$n] = $c; unset($list[$n]); continue; } // Clear out the temporary class list $this->_tmpclasses = []; // If it's loaded, register it and remove it from the list! if ($c->isInstalled() && $c->isLoadable() && $c->loadFiles()) { try{ // Allow for on-the-fly package upgrading regardless of DEV mode or not. if ($c->needsUpdated()) { // Load this component's classes in case an upgrade operation requires one. // This allows a component to be loaded partially without completely being loaded. $this->_tmpclasses = $c->getClassList(); // Lock the site first! // This is because some upgrade procedures take a long time to upgrade. file_put_contents(TMP_DIR . 'lock.message', 'Core Plus is being upgraded, please try again in a minute. '); $c->upgrade(); unlink(TMP_DIR . 'lock.message'); } } catch(Exception $e){ SystemLogModel::LogErrorEvent('/core/component/failedupgrade', 'Ignoring component [' . $n . '] due to an error during upgrading!', $e->getMessage()); unlink(TMP_DIR . 'lock.message'); //$c->disable(); $this->_componentsDisabled[$n] = $c; unset($list[$n]); continue; } try{ $this->_components[$n] = $c; $this->_registerComponent($c); $c->loadSupplementalModels(); } catch(Exception $e){ SystemLogModel::LogErrorEvent('/core/component/failedregister', 'Ignoring component [' . $n . '] due to an error during registration!', $e->getMessage()); //$c->disable(); $this->_componentsDisabled[$n] = $c; unset($list[$n]); continue; } unset($list[$n]); continue; } // Allow for on-the-fly package upgrading regardless of DEV mode or not. // Guess this is needed for the loadFiles part... if ($c->isInstalled() && $c->needsUpdated() && $c->isLoadable()) { // Lock the site first! // This is because some upgrade procedures take a long time to upgrade. file_put_contents(TMP_DIR . 'lock.message', 'Core Plus is being upgraded, please try again in a minute. '); $c->upgrade(); $c->loadFiles(); $this->_components[$n] = $c; $this->_registerComponent($c); unlink(TMP_DIR . 'lock.message'); unset($list[$n]); continue; } // Allow packages to be auto-installed if in DEV mode. // If DEV mode is not enabled, just install the new component, do not enable it. if (!$c->isInstalled() && $c->isLoadable()) { // Load this component's classes in case an install operation requires one. // This allows a component to be loaded partially without completely being loaded. $this->_tmpclasses = $c->getClassList(); // w00t $c->install(); // BLAH, until I fix the disabled-packages-not-viewable bug... $c->enable(); $c->loadFiles(); $this->_components[$n] = $c; $this->_registerComponent($c); /* if(!DEVELOPMENT_MODE){ $c->disable(); } else{ $c->enable(); $c->loadFiles(); $this->_components[$n] = $c; $this->_registerComponent($c); } */ unset($list[$n]); continue; } } } while ($size > 0 && ($size != sizeof($list))); // If dev mode is enabled, display a list of components installed but not loadable. foreach ($list as $n => $c) { //$this->_components[$n] = $c; $this->_componentsDisabled[$n] = $c; // Ignore anything with the execmode different, those should be minor notices for debugging if anything. if ($c->error & Component_2_1::ERROR_WRONGEXECMODE) continue; if (DEVELOPMENT_MODE) { SystemLogModel::LogErrorEvent('/core/component/missingrequirement', 'Could not load installed component ' . $n . ' due to requirement failed.', $c->getErrors()); } } // Don't forget to load the themes too! if(class_exists('ThemeHandler')){ foreach(ThemeHandler::GetAllThemes() as $theme){ /** @var $theme Theme */ $theme->load(); } } // Lastly, make sure that the template path cache is updated! if(class_exists('\\Core\\Templates\\Template')){ \Core\Templates\Template::RequeryPaths(); } }
/** * Render the View to the browser. */ public function render(){ \Core\Utilities\Profiler\Profiler::GetDefaultProfiler()->record('Starting PageRequest->render()'); $view = $this->getView(); $page = $this->getPageModel(); // Dispatch the hooks here if it's a 404 or 403. if ($view->error == View::ERROR_ACCESSDENIED || $view->error == View::ERROR_NOTFOUND) { // Let other things chew through it... (optionally) HookHandler::DispatchHook('/core/page/error-' . $view->error, $view); } try { // This will pre-fetch the contents of the entire page and store it into memory. // If it is cacheable, then it will be cached and used for the next execution. // If the user has the view user activity permission, add the link to that page! if(\Core\user()->checkAccess('p:user_activity_list') && $page && $page->exists()){ $view->addControl( 'User Activity Details', '/useractivity/details?filter[baseurl]=' . $page->get('baseurl'), 'eye' ); } $view->fetch(); } catch (Exception $e) { // If something happens in the rendering of the template... consider it a server error. $view->error = View::ERROR_SERVERERROR; $view->baseurl = '/error/error/500'; $view->setParameters(array()); $view->templatename = '/pages/error/error500.tpl'; $view->mastertemplate = ConfigHandler::Get('/theme/default_template'); $view->assignVariable('exception', $e); \Core\ErrorManagement\exception_handler($e); $view->fetch(); } if($this->isCacheable()){ $uakey = \Core\UserAgent::Construct()->getPseudoIdentifier(); $urlkey = $this->host . $this->uri; $expires = $page->get('expires'); // Number of seconds. $key = 'page-cache-' . md5($urlkey . '-' . $uakey); $d = new \Core\Date\DateTime(); $d->modify('+' . $expires . ' seconds'); $view->headers['Cache-Control'] = 'max-age=' . $expires; $view->headers['Expires'] = $d->format('r', \Core\Date\Timezone::TIMEZONE_GMT); $view->headers['Vary'] = 'Accept-Encoding,User-Agent,Cookie'; $view->headers['X-Core-Cached-Date'] = \Core\Date\DateTime::NowGMT('r'); $view->headers['X-Core-Cached-Server'] = 1; // @todo Implement multi-server support. $view->headers['X-Core-Cached-Render-Time'] = \Core\Utilities\Profiler\Profiler::GetDefaultProfiler()->getTimeFormatted(); // Record the actual View into cache. \Core\Cache::Set($key, $view, $expires); // And record the key onto an index cache record so there's a record of what to delete on updates. $indexkey = $page->getIndexCacheKey(); $index = \Core\Cache::Get($indexkey, SECONDS_ONE_DAY); if(!$index){ $index = []; } $index[] = $key; \Core\Cache::Set($indexkey, $index, SECONDS_ONE_DAY); } elseif(($reason = $this->isNotCacheableReason()) !== null){ $view->headers['X-Core-NotCached-Reason'] = $reason; } $view->headers['X-Core-Render-Time'] = \Core\Utilities\Profiler\Profiler::GetDefaultProfiler()->getTimeFormatted(); $view->render(); // Make sure I update any existing page now that the controller has ran. if ($page && $page->exists() && $view->error == View::ERROR_NOERROR) { // Only increase the pageview count if the visitor is not a bot. // UA detection isn't very accurate, but this isn't for precision accuracy, merely a rough estimate. if(!\Core\UserAgent::Construct()->isBot()){ $page->set('pageviews', $page->get('pageviews') + 1); } $page->set('last_template', $view->templatename); $page->set('body', $view->fetchBody()); $page->save(); } // Just before the page stops execution... HookHandler::DispatchHook('/core/page/postrender'); }