| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- <?php
- /*
- * This file is part of MODX Revolution.
- *
- * Copyright (c) MODX, LLC. All Rights Reserved.
- *
- * For complete copyright and license information, see the COPYRIGHT and LICENSE
- * files found in the top-level directory of this distribution.
- */
- /**
- * The lexicon handling class. Handles all lexicon topics by loading and storing their entries into a cached array.
- * Also considers database-based overrides for specific lexicon entries that preserve the originals and allow reversion.
- *
- * @package modx
- */
- class modLexicon {
- /**
- * Reference to the MODX instance.
- *
- * @var modX $modx
- * @access protected
- */
- public $modx = null;
- /**
- * The actual language array.
- *
- * @todo Separate into separate arrays for each namespace (and maybe topic)
- * so that no namespacing in lexicon entries is needed. Maybe keep a master
- * array of entries, but then have subarrays for topic-specific referencing.
- *
- * @var array $_lexicon
- * @access protected
- */
- protected $_lexicon = array();
- /**
- * Directories to search for language strings in.
- *
- * @deprecated
- * @var array $_paths
- * @access protected
- */
- protected $_paths = array();
- /**
- * An array of loaded topic strings
- *
- * @var array $_loadedTopics
- */
- protected $_loadedTopics = array();
- /**
- * Creates the modLexicon instance.
- *
- * @constructor
- * @param xPDO $modx A reference to the modX instance.
- * @param array $config An array of configuration properties
- */
- function __construct(xPDO &$modx,array $config = array()) {
- $this->modx =& $modx;
- $this->_paths = array(
- 'core' => $this->modx->getOption('core_path') . 'cache/lexicon/',
- );
- $this->_lexicon = array($this->modx->getOption('cultureKey',null,'en') => array());
- $this->config = array_merge($config,array());
- }
- /**
- * Clears the lexicon cache for the specified path.
- *
- * @access public
- * @param string $path The path to clear.
- * @return string The results of the cache clearing.
- */
- public function clearCache($path = '') {
- $path = 'lexicon/'.$path;
- return $this->modx->cacheManager->refresh(array(
- 'lexicon_topics' => array($path),
- ));
- }
- /**
- * Returns if the key exists in the lexicon.
- *
- * @access public
- * @param string $index
- * @return boolean True if exists.
- */
- public function exists($index,$language = '') {
- $language = !empty($language) ? $language : $this->modx->getOption('cultureKey',null,'en');
- return (is_string($index) && isset($this->_lexicon[$language][$index]));
- }
- /**
- * Accessor method for the lexicon array.
- *
- * @access public
- * @param string $prefix If set, will only return the lexicon entries with this prefix.
- * @param boolean $removePrefix If true, will strip the prefix from the returned indexes
- * @param string $language
- * @return array The internal lexicon.
- */
- public function fetch($prefix = '',$removePrefix = false,$language = '') {
- $language = !empty($language) ? $language : $this->modx->getOption('cultureKey',null,'en');
- if (!empty($prefix)) {
- $lex = array();
- $lang = $this->_lexicon[$language];
- if (is_array($lang)) {
- foreach ($lang as $k => $v) {
- if (strpos($k,$prefix) !== false) {
- $key = $removePrefix ? str_replace($prefix,'',$k) : $k;
- $lex[$key] = $v;
- }
- }
- }
- return $lex;
- }
- return $this->_lexicon[$language];
- }
- /**
- * Return the cache key representing the specified lexicon topic.
- *
- * @access public
- * @param string $namespace The namespace for the topic
- * @param string $topic The topic to grab
- * @param string $language The language for the topic
- * @return string The cache key for the specified topic
- */
- public function getCacheKey($namespace = 'core',$topic = 'default',$language = '') {
- if (empty($namespace)) $namespace = 'core';
- if (empty($topic)) $topic = 'default';
- if (empty($language)) $language = $this->modx->getOption('cultureKey',null,'en');
- return 'lexicon/'.$language.'/'.$namespace.'/'.$topic;
- }
- /**
- * Loads a variable number of topic areas. They must reside as topicname.
- * inc.php files in their proper culture directory. Can load an infinite
- * number of topic areas via a dynamic number of arguments.
- *
- * They are loaded by language:namespace:topic, namespace:topic, or just
- * topic. Examples: $modx->lexicon->load('en:core:snippet'); $modx->lexicon-
- * >load ('demo:test'); $modx->lexicon->load('chunk');
- *
- * @access public
- */
- public function load() {
- $topics = func_get_args(); /* allow for dynamic number of lexicons to load */
- if ($this->modx->context && $this->modx->context->get('key') == 'mgr') {
- $defaultLanguage = $this->modx->getOption('manager_language',null,$this->modx->getOption('cultureKey',null,'en'));
- } else {
- $defaultLanguage = $this->modx->getOption('cultureKey',null,'en');
- }
- foreach ($topics as $topicStr) {
- if (!is_string($topicStr) || $topicStr == '') continue;
- if (in_array($topicStr,$this->_loadedTopics)) continue;
- $nspos = strpos($topicStr,':');
- $topic = str_replace('.','/',$topicStr); /** @deprecated 2.0.0 Allow for lexicon subdirs */
- /* if no namespace, search all lexicons */
- if ($nspos === false) {
- foreach ($this->_paths as $namespace => $path) {
- $entries = $this->loadCache($namespace,$topic);
- if (is_array($entries)) {
- if (!array_key_exists($defaultLanguage,$this->_lexicon)) $this->_lexicon[$defaultLanguage] = array();
- $this->_lexicon[$defaultLanguage] = is_array($this->_lexicon[$defaultLanguage]) ? array_merge($this->_lexicon[$defaultLanguage],$entries) : $entries;
- }
- }
- } else { /* if namespace, search specified lexicon */
- $params = explode(':',$topic);
- if (count($params) <= 2) {
- $language = $defaultLanguage;
- $namespace = $params[0];
- $topic_parsed = $params[1];
- } else {
- $language = $params[0];
- $namespace = $params[1];
- $topic_parsed = $params[2];
- }
- $englishEntries = $language != 'en' ? $this->loadCache($namespace,$topic_parsed,'en') : false;
- $entries = $this->loadCache($namespace,$topic_parsed,$language);
- if (!is_array($entries)) {
- if (is_string($entries) && !empty($entries)) $entries = $this->modx->fromJSON($entries);
- if (empty($entries)) $entries = array();
- }
- if (is_array($englishEntries) && !empty($englishEntries)) {
- $entries = array_merge($englishEntries,$entries);
- }
- if (is_array($entries)) {
- $this->_loadedTopics[] = $topicStr;
- if (!array_key_exists($language,$this->_lexicon)) $this->_lexicon[$language] = array();
- $this->_lexicon[$language] = is_array($this->_lexicon[$language]) ? array_merge($this->_lexicon[$language], $entries) : $entries;
- }
- }
- }
- }
- /**
- * Loads a lexicon topic from the cache. If not found, tries to generate a
- * cache file from the database.
- *
- * @access public
- * @param string $namespace The namespace to load from. Defaults to 'core'.
- * @param string $topic The topic to load. Defaults to 'default'.
- * @param string $language The language to load. Defaults to 'en'.
- * @return array The loaded lexicon array.
- */
- public function loadCache($namespace = 'core', $topic = 'default', $language = '') {
- if (empty($language)) $language = $this->modx->getOption('cultureKey',null,'en');
- $key = $this->getCacheKey($namespace, $topic, $language);
- $enableCache = ($namespace != 'core' && !$this->modx->getOption('cache_noncore_lexicon_topics',null,true)) ? false : true;
- if (!$this->modx->cacheManager) {
- $this->modx->getCacheManager();
- }
- $cached = $this->modx->cacheManager->get($key, array(
- xPDO::OPT_CACHE_KEY => $this->modx->getOption('cache_lexicon_topics_key', null, 'lexicon_topics'),
- xPDO::OPT_CACHE_HANDLER => $this->modx->getOption('cache_lexicon_topics_handler', null, $this->modx->getOption(xPDO::OPT_CACHE_HANDLER)),
- xPDO::OPT_CACHE_FORMAT => (integer) $this->modx->getOption('cache_lexicon_topics_format', null, $this->modx->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP)),
- ));
- if (!$enableCache || $cached == null) {
- $results= false;
- /* load file-based lexicon */
- $results = $this->getFileTopic($language,$namespace,$topic);
- if ($results === false) { /* default back to en */
- $results = $this->getFileTopic('en',$namespace,$topic);
- if ($results === false) {
- $results = array();
- }
- }
- /* get DB overrides */
- $c= $this->modx->newQuery('modLexiconEntry');
- $c->innerJoin('modNamespace','Namespace');
- $c->where(array(
- 'modLexiconEntry.topic' => $topic,
- 'modLexiconEntry.language' => $language,
- 'Namespace.name' => $namespace,
- ));
- $c->sortby($this->modx->getSelectColumns('modLexiconEntry','modLexiconEntry','',array('name')),'ASC');
- $entries= $this->modx->getCollection('modLexiconEntry',$c);
- if (!empty($entries)) {
- /** @var modLexiconEntry $entry */
- foreach ($entries as $entry) {
- $results[$entry->get('name')]= $entry->get('value');
- }
- }
- if ($enableCache) {
- $cached = $this->modx->cacheManager->generateLexiconTopic($key,$results);
- } else {
- $cached = $results;
- }
- }
- if (empty($cached)) {
- $this->modx->log(xPDO::LOG_LEVEL_DEBUG, "An error occurred while trying to cache {$key} (lexicon/language/namespace/topic)");
- }
- return $cached;
- }
- /**
- * Get entries from file-based lexicon topic
- *
- * @param string $language The language to filter by.
- * @param string $namespace The namespace to filter by.
- * @param string $topic The topic to filter by.
- * @return array An array of lexicon entries in key - value pairs for the specified filter.
- */
- public function getFileTopic($language = 'en',$namespace = 'core',$topic = 'default') {
- $corePath = $this->getNamespacePath($namespace);
- $corePath = str_replace(array(
- '{base_path}',
- '{core_path}',
- '{assets_path}',
- ),array(
- $this->modx->getOption('base_path'),
- $this->modx->getOption('core_path'),
- $this->modx->getOption('assets_path'),
- ),$corePath);
- $topicPath = str_replace('//','/',$corePath.'/lexicon/'.$language.'/'.$topic.'.inc.php');
- $results = array();
- $_lang = array();
- if (file_exists($topicPath)) {
- include $topicPath;
- $results = $_lang;
- } else {
- return false;
- }
- return $results;
- }
- /**
- * Get the path of the specified Namespace
- *
- * @param string $namespace The key of the Namespace
- * @return string The path for the Namespace
- */
- public function getNamespacePath($namespace = 'core') {
- $corePath = $this->modx->getOption('core_path',null,MODX_CORE_PATH);
- if ($namespace != 'core') {
- /** @var modNamespace $namespaceObj */
- $namespaceObj = $this->modx->getObject('modNamespace',$namespace);
- if ($namespaceObj) {
- $corePath = $namespaceObj->getCorePath();
- }
- }
- return $corePath;
- }
- /**
- * Get a list of available Topics when given a Language and Namespace.
- *
- * @param string $language The language to filter by.
- * @param string $namespace The language to filter by.
- * @return array An array of Topic names.
- */
- public function getTopicList($language = 'en',$namespace = 'core') {
- $corePath = $this->getNamespacePath($namespace);
- $lexPath = str_replace('//','/',$corePath.'/lexicon/'.$language.'/');
- $topics = array();
- if (!is_dir($lexPath)) return $topics;
- /** @var DirectoryIterator $topic */
- foreach (new DirectoryIterator($lexPath) as $topic) {
- if (in_array($topic,array('.','..','.svn','.git','_notes'))) continue;
- if (!$topic->isReadable()) continue;
- if ($topic->isFile()) {
- $fileName = $topic->getFilename();
- if (strpos($fileName,'.inc.php')) {
- $topics[] = str_replace('.inc.php','',$fileName);
- }
- }
- }
- $c = $this->modx->newQuery('modLexiconEntry');
- $c->where(array(
- 'namespace' => $namespace,
- 'topic:NOT IN' => $topics,
- ));
- $c->select(array('topic'));
- $c->query['distinct'] = 'DISTINCT';
- if ($c->prepare() && $c->stmt->execute()) {
- $entries = $c->stmt->fetchAll(\PDO::FETCH_ASSOC);
- if (is_array($entries) and count($entries) > 0) {
- foreach ($entries as $v) {
- $topics[] = $v['topic'];
- }
- }
- }
- sort($topics);
- return $topics;
- }
- /**
- * Get a list of available languages for a Namespace.
- *
- * @param string $namespace The Namespace to filter by.
- * @return array An array of available languages
- */
- public function getLanguageList($namespace = 'core') {
- $corePath = $this->getNamespacePath($namespace);
- $lexPath = str_replace('//','/',$corePath.'/lexicon/');
- if (!is_dir($lexPath)) {
- return array();
- }
- $languages = array();
- /** @var DirectoryIterator $language */
- foreach (new DirectoryIterator($lexPath) as $language) {
- if (in_array($language,array('.','..','.svn','.git','_notes','country'))) continue;
- if (!$language->isReadable()) continue;
- if ($language->isDir()) {
- $languages[] = $language->getFilename();
- }
- }
- $c = $this->modx->newQuery('modLexiconEntry');
- $c->where(array(
- 'namespace' => $namespace,
- 'language:NOT IN' => $languages,
- ));
- $c->select(array('language'));
- $c->query['distinct'] = 'DISTINCT';
- if ($c->prepare() && $c->stmt->execute()) {
- $entries = $c->stmt->fetchAll(\PDO::FETCH_ASSOC);
- if (is_array($entries) and count($entries) > 0) {
- foreach ($entries as $v) {
- $languages[] = $v['language'];
- }
- }
- }
- sort($languages);
- return $languages;
- }
- /**
- * Get a lexicon string by its index.
- *
- * @access public
- * @param string $key The key of the lexicon string.
- * @param array $params An assocative array of placeholder
- * keys and values to parse
- * @param string $language
- * @return string The text of the lexicon key, blank if not found.
- */
- public function process($key,array $params = array(),$language = '') {
- $language = !empty($language) ? $language : $this->modx->getOption('cultureKey',null,'en');
- /* make sure key exists */
- if (!is_string($key) || !isset($this->_lexicon[$language][$key])) {
- $this->modx->log(xPDO::LOG_LEVEL_DEBUG,'Language string not found: "'.$key.'"');
- return $key;
- }
- /* if params are passed, allow for parsing of [[+key]] values to strings */
- return empty($params)
- ? $this->_lexicon[$language][$key]
- : $this->_parse($this->_lexicon[$language][$key],$params);
- }
- /**
- * Sets a lexicon key to a value. Not recommended, since doesn't query the
- * database.
- *
- * @access public
- * @param string|array $keys Either an array of array pairs of key/values or
- * a key string.
- * @param string $text The text to set, if the first parameter is a string.
- * @param string $language The language to set the key in. Defaults to current.
- */
- public function set($keys, $text = '', $language = '') {
- $language = !empty($language) ? $language : $this->modx->getOption('cultureKey',null,$language);
- if (is_array($keys)) {
- foreach ($keys as $key => $str) {
- if ($key == '') continue;
- $this->_lexicon[$language][$key] = $str;
- }
- } else if (is_string($keys) && $keys != '') {
- $this->_lexicon[$language][$keys] = $text;
- }
- }
- /**
- * Parses a lexicon string, replacing placeholders with
- * specified strings.
- *
- * @access private
- * @param string $str The string to parse
- * @param array $params An associative array of keys to replace
- * @return string The processed string
- */
- private function _parse($str,$params) {
- if (!$str) return '';
- if (empty($params)) return $str;
- foreach ($params as $k => $v) {
- $str = str_replace('[[+'.$k.']]',$v,$str);
- }
- return $str;
- }
- /**
- * Returns the total # of entries in the active lexicon
- * @param string $language
- * @return int
- */
- public function total($language = '') {
- $language = !empty($language) ? $language : $this->modx->getOption('cultureKey',null,'en');
- return count($this->_lexicon[$language]);
- }
- /**
- * Completely clears the lexicon
- * @param string $language
- * @return void
- */
- public function clear($language = '') {
- if (!empty($language)) {
- $this->_lexicon[$language] = array();
- } else {
- $this->_lexicon = array($this->modx->getOption('cultureKey',null,'en') => array());
- }
- }
- }
|