├── assets └── components │ └── tagger │ ├── index.html │ ├── js │ ├── index.html │ └── mgr │ │ ├── tagger.js │ │ ├── sections │ │ └── home.js │ │ ├── widgets │ │ ├── home.panel.js │ │ ├── tag │ │ │ └── tag.window.js │ │ └── group │ │ │ └── group.grid.js │ │ └── extras │ │ ├── griddraganddrop.js │ │ └── tagger.combo.js │ ├── css │ ├── index.html │ ├── mgr.css │ ├── tagfield.css │ └── superboxselect.css │ ├── img │ └── superboxselect │ │ ├── clear.png │ │ ├── close.png │ │ ├── expand.png │ │ └── trigger.png │ └── connector.php ├── core └── components │ └── tagger │ ├── templates │ └── home.tpl │ ├── lexicon │ ├── fr │ │ ├── custom.inc.php │ │ ├── properties.inc.php │ │ └── default.inc.php │ ├── ru │ │ ├── custom.inc.php │ │ ├── properties.inc.php │ │ └── default.inc.php │ ├── en │ │ ├── custom.inc.php │ │ ├── properties.inc.php │ │ └── default.inc.php │ └── de │ │ └── default.inc.php │ ├── bootstrap.php │ ├── src │ ├── Model │ │ ├── TaggerTagResource.php │ │ ├── metadata.mysql.php │ │ ├── TaggerTag.php │ │ ├── TaggerGroup.php │ │ └── mysql │ │ │ ├── TaggerTagResource.php │ │ │ ├── TaggerTag.php │ │ │ └── TaggerGroup.php │ ├── Processors │ │ ├── Tag │ │ │ ├── Remove.php │ │ │ ├── UpdateFromGrid.php │ │ │ ├── UnAssign.php │ │ │ ├── RemoveMultiple.php │ │ │ ├── GetAssignedResources.php │ │ │ ├── Create.php │ │ │ ├── GetList.php │ │ │ ├── Update.php │ │ │ └── Merge.php │ │ ├── Group │ │ │ ├── Remove.php │ │ │ ├── UpdateFromGrid.php │ │ │ ├── GetList.php │ │ │ ├── DDReorder.php │ │ │ ├── Update.php │ │ │ ├── Create.php │ │ │ └── Import.php │ │ └── Extra │ │ │ ├── GetTVs.php │ │ │ └── GetTags.php │ ├── Events │ │ ├── OnPageNotFound.php │ │ ├── Event.php │ │ ├── OnResourceDuplicate.php │ │ ├── OnDocFormPrerender.php │ │ └── OnDocFormSave.php │ ├── Utils.php │ ├── TaggerGateway.php │ └── Tagger.php │ ├── elements │ ├── plugins │ │ └── Tagger.php │ └── snippets │ │ ├── TaggerGetCurrentTag.php │ │ ├── TaggerGetRelatedWhere.php │ │ └── TaggerGetResourcesWhere.php │ ├── index.class.php │ ├── controllers │ └── home.class.php │ └── schema │ └── tagger.mysql.schema.xml ├── .gitignore ├── _build ├── gpm_scripts │ ├── gpm.script.tables.php │ └── gpm.script.sync_tables.php ├── gpm_resolvers │ ├── gpm.resolve.bootstrap.php │ └── gpm.resolve.element_property_set.php ├── scripts │ └── after.migration.php └── gpm.json ├── README.md └── CHANGELOG.md /assets/components/tagger/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/components/tagger/js/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/components/tagger/css/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/components/tagger/templates/home.tpl: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build/build.config.php 2 | config.core.php 3 | .idea 4 | .settings 5 | nbproject 6 | .project 7 | _packages 8 | -------------------------------------------------------------------------------- /assets/components/tagger/img/superboxselect/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modxcms/Tagger/HEAD/assets/components/tagger/img/superboxselect/clear.png -------------------------------------------------------------------------------- /assets/components/tagger/img/superboxselect/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modxcms/Tagger/HEAD/assets/components/tagger/img/superboxselect/close.png -------------------------------------------------------------------------------- /assets/components/tagger/img/superboxselect/expand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modxcms/Tagger/HEAD/assets/components/tagger/img/superboxselect/expand.png -------------------------------------------------------------------------------- /assets/components/tagger/img/superboxselect/trigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modxcms/Tagger/HEAD/assets/components/tagger/img/superboxselect/trigger.png -------------------------------------------------------------------------------- /core/components/tagger/lexicon/fr/custom.inc.php: -------------------------------------------------------------------------------- 1 | addPackage('Tagger\Model', $namespace['path'] . 'src/', null, 'Tagger\\'); 8 | 9 | $modx->services->add('tagger', function($c) use ($modx) { 10 | return new Tagger\Tagger($modx); 11 | }); 12 | -------------------------------------------------------------------------------- /core/components/tagger/src/Model/TaggerTagResource.php: -------------------------------------------------------------------------------- 1 | event->name; 11 | 12 | if (class_exists($className)) { 13 | /** @var \Tagger\Events\Event $handler */ 14 | $handler = new $className($modx, $scriptProperties); 15 | $handler->run(); 16 | } 17 | 18 | return; 19 | -------------------------------------------------------------------------------- /assets/components/tagger/js/mgr/sections/home.js: -------------------------------------------------------------------------------- 1 | tagger.page.Home = function(config) { 2 | config = config || {}; 3 | Ext.applyIf(config,{ 4 | components: [ 5 | { 6 | xtype: 'tagger-panel-home', 7 | renderTo: 'tagger-panel-home' 8 | } 9 | ] 10 | }); 11 | tagger.page.Home.superclass.constructor.call(this,config); 12 | }; 13 | Ext.extend(tagger.page.Home, MODx.Component); 14 | Ext.reg('tagger-page-home', tagger.page.Home); 15 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Tag/Remove.php: -------------------------------------------------------------------------------- 1 | '3.0', 4 | 'namespace' => 'Tagger\\Model', 5 | 'namespacePrefix' => 'Tagger', 6 | 'class_map' => 7 | array ( 8 | 'xPDO\\Om\\xPDOSimpleObject' => 9 | array ( 10 | 0 => 'Tagger\\Model\\TaggerGroup', 11 | 1 => 'Tagger\\Model\\TaggerTag', 12 | ), 13 | 'xPDO\\Om\\xPDOObject' => 14 | array ( 15 | 0 => 'Tagger\\Model\\TaggerTagResource', 16 | ), 17 | ), 18 | ); -------------------------------------------------------------------------------- /core/components/tagger/src/Events/OnPageNotFound.php: -------------------------------------------------------------------------------- 1 | modx->context->get('key') == 'mgr') { 11 | return; 12 | } 13 | 14 | $friendlyURL = $this->modx->getOption('friendly_urls', null, 0); 15 | if ($friendlyURL == 0) { 16 | return; 17 | } 18 | 19 | $gateway = new TaggerGateway($this->modx); 20 | $gateway->init($this->scriptProperties); 21 | $gateway->handleRequest(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Extra/GetTVs.php: -------------------------------------------------------------------------------- 1 | scriptProperties =& $scriptProperties; 20 | $this->modx = $modx; 21 | $this->tagger = $this->modx->services->get('tagger'); 22 | } 23 | 24 | abstract public function run(); 25 | } 26 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Tag/UpdateFromGrid.php: -------------------------------------------------------------------------------- 1 | getProperty('data'); 17 | if (empty($data)) { 18 | return $this->modx->lexicon('invalid_data'); 19 | } 20 | $data = $this->modx->fromJSON($data); 21 | if (empty($data)) { 22 | return $this->modx->lexicon('invalid_data'); 23 | } 24 | $this->setProperties($data); 25 | $this->unsetProperty('data'); 26 | 27 | return parent::initialize(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Group/UpdateFromGrid.php: -------------------------------------------------------------------------------- 1 | getProperty('data'); 17 | if (empty($data)) { 18 | return $this->modx->lexicon('invalid_data'); 19 | } 20 | $data = $this->modx->fromJSON($data); 21 | if (empty($data)) { 22 | return $this->modx->lexicon('invalid_data'); 23 | } 24 | $this->setProperties($data); 25 | $this->unsetProperty('data'); 26 | 27 | return parent::initialize(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /_build/gpm_scripts/gpm.script.tables.php: -------------------------------------------------------------------------------- 1 | xpdo; 18 | 19 | if ($options[xPDOTransport::PACKAGE_ACTION] === xPDOTransport::ACTION_UNINSTALL) return true; 20 | 21 | $manager = $modx->getManager(); 22 | 23 | $manager->createObjectContainer(\Tagger\Model\TaggerGroup::class); 24 | $manager->createObjectContainer(\Tagger\Model\TaggerTag::class); 25 | $manager->createObjectContainer(\Tagger\Model\TaggerTagResource::class); 26 | 27 | return true; 28 | -------------------------------------------------------------------------------- /assets/components/tagger/connector.php: -------------------------------------------------------------------------------- 1 | getOption('tagger.core_path', null, $modx->getOption('core_path', null, MODX_CORE_PATH) . 'components/tagger/'); 12 | $tagger = $modx->getService( 13 | 'tagger', 14 | 'Tagger', 15 | $corePath . 'model/tagger/', 16 | array( 17 | 'core_path' => $corePath 18 | ) 19 | ); 20 | 21 | /* handle request */ 22 | $modx->request->handleRequest( 23 | array( 24 | 'processors_path' => $tagger->getOption('processorsPath', null, $corePath . 'processors/'), 25 | 'location' => '', 26 | ) 27 | ); -------------------------------------------------------------------------------- /_build/gpm_resolvers/gpm.resolve.bootstrap.php: -------------------------------------------------------------------------------- 1 | xpdo; 18 | if ($options[xPDOTransport::PACKAGE_ACTION] !== xPDOTransport::ACTION_INSTALL) return true; 19 | 20 | $bootstrap = $object->getCorePath() . 'bootstrap.php'; 21 | if (file_exists($bootstrap)) { 22 | $namespace = $object->toArray(); 23 | $namespace['path'] = $object->getCorePath(); 24 | $namespace['assets_path'] = $object->getAssetsPath(); 25 | 26 | require $object->getCorePath() . 'bootstrap.php'; 27 | } 28 | 29 | return true; 30 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Tag/UnAssign.php: -------------------------------------------------------------------------------- 1 | getProperty('tag'); 26 | $resource = $this->getProperty('resource'); 27 | 28 | $resource = \Tagger\Utils::explodeAndClean($resource); 29 | 30 | if (empty($tag) || empty($resource)) { 31 | return $this->modx->lexicon($this->objectType . '_err_ns'); 32 | } 33 | 34 | $this->modx->removeCollection($this->classKey, ['tag' => $tag, 'resource:IN' => $resource]); 35 | 36 | return $this->success(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /core/components/tagger/index.class.php: -------------------------------------------------------------------------------- 1 | tagger = $this->modx->services->get('tagger'); 18 | 19 | $this->addCss($this->tagger->getOption('cssUrl') . 'mgr.css'); 20 | $this->addJavascript($this->tagger->getOption('jsUrl') . 'mgr/tagger.js'); 21 | $this->addHtml(' 22 | 27 | '); 28 | 29 | parent::initialize(); 30 | } 31 | 32 | public function getLanguageTopics() 33 | { 34 | return ['tagger:default']; 35 | } 36 | 37 | public function checkPermissions() 38 | { 39 | return true; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Tag/RemoveMultiple.php: -------------------------------------------------------------------------------- 1 | getProperty('tags', ''); 26 | $tags = \Tagger\Utils::explodeAndClean($tags); 27 | 28 | if (empty($tags)) { 29 | return $this->failure($this->modx->lexicon('tagger.err.tags_ns')); 30 | } 31 | 32 | foreach ($tags as $tag) { 33 | /** @var TaggerTag $tag */ 34 | $tagObject = $this->modx->getObject($this->classKey, $tag); 35 | 36 | if (empty($tagObject)) { 37 | continue; 38 | } 39 | 40 | $tagObject->remove(); 41 | } 42 | 43 | return $this->success(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /core/components/tagger/src/Utils.php: -------------------------------------------------------------------------------- 1 | xpdo; 17 | 18 | // http://forums.modx.com/thread/88734/package-version-check#dis-post-489104 19 | $c = $modx->newQuery(modTransportPackage::class); 20 | $c->where(array( 21 | 'workspace' => 1, 22 | "(SELECT 23 | `signature` 24 | FROM {$modx->getTableName(modTransportPackage::class)} AS `latestPackage` 25 | WHERE `latestPackage`.`package_name` = `modTransportPackage`.`package_name` 26 | ORDER BY 27 | `latestPackage`.`version_major` DESC, 28 | `latestPackage`.`version_minor` DESC, 29 | `latestPackage`.`version_patch` DESC, 30 | IF(`release` = '' OR `release` = 'ga' OR `release` = 'pl','z',`release`) DESC, 31 | `latestPackage`.`release_index` DESC 32 | LIMIT 1,1) = `modTransportPackage`.`signature`", 33 | )); 34 | $c->where(array( 35 | 'modTransportPackage.package_name' => 'tagger', 36 | 'installed:IS NOT' => null 37 | )); 38 | 39 | /** @var modTransportPackage $oldPackage */ 40 | $oldPackage = $modx->getObject(modTransportPackage::class, $c); 41 | 42 | //if ($oldPackage && $oldPackage->compareVersion('1.10.0-pl', '>')) { 43 | // 44 | //} 45 | 46 | return true; 47 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Tag/GetAssignedResources.php: -------------------------------------------------------------------------------- 1 | getProperty('tagId'); 32 | 33 | if (empty($tagId) || $tagId == 0) { 34 | return $this->modx->lexicon('tagger.err.tag_assigned_resources_tag_ns'); 35 | } 36 | 37 | return parent::beforeQuery(); 38 | } 39 | 40 | public function prepareQueryBeforeCount(xPDOQuery $c) 41 | { 42 | $query = $this->getProperty('query'); 43 | $tagId = $this->getProperty('tagId'); 44 | 45 | $c->leftJoin(TaggerTagResource::class, 'TagResource', ['modResource.id = TagResource.resource']); 46 | $c->where( 47 | [ 48 | 'TagResource.tag' => $tagId, 49 | ] 50 | ); 51 | 52 | if (!empty($query)) { 53 | $c->where( 54 | [ 55 | 'pagetitle:LIKE' => '%' . $query . '%', 56 | ] 57 | ); 58 | } 59 | 60 | return $c; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Tag/Create.php: -------------------------------------------------------------------------------- 1 | getProperty('tag'); 29 | $group = $this->getProperty('group'); 30 | $alias = $this->getProperty('alias'); 31 | 32 | if (empty($name) || empty($group)) { 33 | if (empty($group)) { 34 | $this->addFieldError('group', $this->modx->lexicon('tagger.err.group_name_ns')); 35 | } 36 | 37 | if (empty($name)) { 38 | $this->addFieldError('tag', $this->modx->lexicon('tagger.err.tag_name_ns')); 39 | } 40 | } else { 41 | if ($this->doesAlreadyExist(['tag' => $name, 'group' => $group])) { 42 | $this->addFieldError('tag', $this->modx->lexicon('tagger.err.tag_name_ae')); 43 | } 44 | } 45 | 46 | if (!empty($alias)) { 47 | $alias = $this->object->cleanAlias($alias); 48 | if ($this->doesAlreadyExist(['alias' => $alias, 'group' => $group])) { 49 | $this->addFieldError('alias', $this->modx->lexicon('tagger.err.tag_alias_ae')); 50 | } 51 | } 52 | 53 | return parent::beforeSave(); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Extra/GetTags.php: -------------------------------------------------------------------------------- 1 | getProperty('group'); 23 | $limit = $this->getProperty('limit', 20); 24 | $start = $this->getProperty('start', 0); 25 | $sortField = $this->getProperty('sort_field', 'alias'); 26 | $sortDir = $this->getProperty('sort_dir', 'asc'); 27 | $tags = $this->getProperty('tags', []); 28 | 29 | $c = $this->modx->newQuery(TaggerTag::class); 30 | $c->where(['group' => $group]); 31 | 32 | $query = $this->getProperty('query'); 33 | 34 | if (!empty($query)) { 35 | $c->where( 36 | [ 37 | 'tag:LIKE' => '%' . $query . '%', 38 | ] 39 | ); 40 | } 41 | 42 | if (!empty($tags)) { 43 | $c->where([ 44 | 'tag:in' => $tags 45 | ]); 46 | } 47 | 48 | $cnt = $this->modx->getCount(TaggerTag::class, $c); 49 | 50 | $c->select($this->modx->getSelectColumns(TaggerTag::class, 'TaggerTag', '', ['tag'])); 51 | $c->limit($limit, $start); 52 | $c->sortby($sortField, $sortDir); 53 | 54 | $c->prepare(); 55 | $c->stmt->execute(); 56 | 57 | $returnArray = []; 58 | 59 | while ($tag = $c->stmt->fetch(\PDO::FETCH_ASSOC)) { 60 | $returnArray[] = ['tag' => $tag['tag']]; 61 | } 62 | 63 | return $this->outputArray($returnArray, $cnt); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /assets/components/tagger/js/mgr/widgets/home.panel.js: -------------------------------------------------------------------------------- 1 | tagger.panel.Home = function(config) { 2 | config = config || {}; 3 | Ext.apply(config,{ 4 | border: false, 5 | baseCls: 'modx-formpanel', 6 | cls: 'container', 7 | items: [{ 8 | html: '

'+_('tagger')+'

', 9 | border: false, 10 | cls: 'modx-page-header' 11 | },{ 12 | xtype: 'modx-tabs', 13 | defaults: { 14 | border: false, 15 | autoHeight: true, 16 | layout: 'anchor' 17 | }, 18 | border: true, 19 | activeItem: 0, 20 | hideMode: 'offsets', 21 | items: [{ 22 | title: _('tagger.tag.tags'), 23 | items: [{ 24 | html: '

'+_('tagger.tag.intro_msg')+'

', 25 | border: false, 26 | bodyCssClass: 'panel-desc' 27 | },{ 28 | xtype: 'tagger-grid-tag', 29 | preventRender: true, 30 | cls: 'main-wrapper', 31 | anchor: '100%' 32 | }] 33 | },{ 34 | title: _('tagger.group.groups'), 35 | items: [{ 36 | html: '

'+_('tagger.group.intro_msg')+'

', 37 | border: false, 38 | bodyCssClass: 'panel-desc' 39 | },{ 40 | xtype: 'tagger-grid-group', 41 | preventRender: true, 42 | cls: 'main-wrapper', 43 | anchor: '100%' 44 | }] 45 | }] 46 | }] 47 | }); 48 | tagger.panel.Home.superclass.constructor.call(this,config); 49 | }; 50 | Ext.extend(tagger.panel.Home,MODx.Panel); 51 | Ext.reg('tagger-panel-home',tagger.panel.Home); 52 | -------------------------------------------------------------------------------- /core/components/tagger/controllers/home.class.php: -------------------------------------------------------------------------------- 1 | modx->lexicon('tagger'); 21 | } 22 | 23 | public function loadCustomCssJs() 24 | { 25 | $this->addCss($this->tagger->getOption('cssUrl') . 'superboxselect.css'); 26 | 27 | $this->addJavascript($this->tagger->getOption('jsUrl') . 'mgr/extras/tagger.combo.js'); 28 | $this->addJavascript($this->tagger->getOption('jsUrl') . 'mgr/extras/tagger.tagfield.js'); 29 | $this->addJavascript($this->tagger->getOption('jsUrl') . 'mgr/extras/griddraganddrop.js'); 30 | 31 | $this->addJavascript($this->tagger->getOption('jsUrl') . 'mgr/widgets/tag/tag.grid.js'); 32 | $this->addJavascript($this->tagger->getOption('jsUrl') . 'mgr/widgets/tag/tag.window.js'); 33 | 34 | $this->addJavascript($this->tagger->getOption('jsUrl') . 'mgr/widgets/group/group.grid.js'); 35 | $this->addJavascript($this->tagger->getOption('jsUrl') . 'mgr/widgets/group/group.window.js'); 36 | $this->addJavascript($this->tagger->getOption('jsUrl') . 'mgr/widgets/home.panel.js'); 37 | $this->addLastJavascript($this->tagger->getOption('jsUrl') . 'mgr/sections/home.js'); 38 | 39 | $this->addHtml(' 40 | 45 | '); 46 | } 47 | 48 | public function getTemplateFile() 49 | { 50 | return $this->tagger->getOption('templatesPath') . 'home.tpl'; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Tag/GetList.php: -------------------------------------------------------------------------------- 1 | setDefaultProperties( 33 | [ 34 | 'forTagfield' => false, 35 | ] 36 | ); 37 | 38 | return $initialized; 39 | } 40 | 41 | public function prepareQueryBeforeCount(xPDOQuery $c) 42 | { 43 | $query = $this->getProperty('query'); 44 | 45 | if (!empty($query)) { 46 | $c->where( 47 | [ 48 | 'tag:LIKE' => '%' . $query . '%', 49 | ] 50 | ); 51 | } 52 | 53 | $group = intval($this->getProperty('group')); 54 | 55 | if (!empty($group)) { 56 | $c->where( 57 | [ 58 | 'group' => $group, 59 | ] 60 | ); 61 | } 62 | 63 | return $c; 64 | } 65 | 66 | public function outputArray(array $array, $count = false) 67 | { 68 | if ($count === false) { 69 | $count = count($array); 70 | } 71 | 72 | $forTagfield = $this->getProperty('forTagfield', false); 73 | 74 | if ($forTagfield == true) { 75 | return '{"success": true, "total":"' . $count . '","results":' . $this->modx->toJSON($array) . '}'; 76 | } 77 | 78 | return '{"success": true, "total":"' . $count . '","results":' . $this->modx->toJSON($array) . '}'; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Group/GetList.php: -------------------------------------------------------------------------------- 1 | setDefaultProperties( 33 | [ 34 | 'addNone' => false, 35 | ] 36 | ); 37 | 38 | return $initialized; 39 | } 40 | 41 | public function beforeIteration(array $list) 42 | { 43 | if ($this->getProperty('addNone', false)) { 44 | $list[] = ['id' => 0, 'name' => $this->modx->lexicon('tagger.group.all')]; 45 | } 46 | return $list; 47 | } 48 | 49 | public function prepareQueryBeforeCount(xPDOQuery $c) 50 | { 51 | $query = $this->getProperty('query'); 52 | 53 | 54 | if (!empty($query)) { 55 | $c->where( 56 | [ 57 | 'name:LIKE' => '%' . $query . '%', 58 | ] 59 | ); 60 | } 61 | 62 | $fieldType = $this->getProperty('fieldType'); 63 | if ($fieldType) { 64 | $c->where( 65 | [ 66 | 'field_type' => $fieldType, 67 | ] 68 | ); 69 | } 70 | 71 | 72 | return $c; 73 | } 74 | 75 | public function outputArray(array $array, $count = false) 76 | { 77 | if ($count === false) { 78 | $count = count($array); 79 | } 80 | return '{"success": true, "total":"' . $count . '","results":' . $this->modx->toJSON($array) . '}'; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Group/DDReorder.php: -------------------------------------------------------------------------------- 1 | getProperty('idGroup'); 26 | $oldIndex = $this->getProperty('oldIndex'); 27 | $newIndex = $this->getProperty('newIndex'); 28 | 29 | $groups = $this->modx->newQuery($this->classKey); 30 | $groups->where( 31 | [ 32 | 'id:!=' => $idGroup, 33 | 'position:>=' => min($oldIndex, $newIndex), 34 | 'position:<=' => max($oldIndex, $newIndex), 35 | ] 36 | ); 37 | 38 | $groups->sortby('position', 'ASC'); 39 | 40 | $groupsCollection = $this->modx->getCollection($this->classKey, $groups); 41 | 42 | if (min($oldIndex, $newIndex) == $newIndex) { 43 | foreach ($groupsCollection as $group) { 44 | $groupObject = $this->modx->getObject($this->classKey, $group->get('id')); 45 | $groupObject->set('position', $groupObject->get('position') + 1); 46 | $groupObject->save(); 47 | } 48 | } else { 49 | foreach ($groupsCollection as $group) { 50 | $groupObject = $this->modx->getObject($this->classKey, $group->get('id')); 51 | $groupObject->set('position', $groupObject->get('position') - 1); 52 | $groupObject->save(); 53 | } 54 | } 55 | 56 | $groupObject = $this->modx->getObject($this->classKey, $idGroup); 57 | $groupObject->set('position', $newIndex); 58 | $groupObject->save(); 59 | 60 | 61 | return $this->success('', $groupObject); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Group/Update.php: -------------------------------------------------------------------------------- 1 | getProperty('name'); 29 | $alias = $this->getProperty('alias'); 30 | 31 | if (empty($name)) { 32 | $this->addFieldError('name', $this->modx->lexicon('tagger.err.group_name_ns')); 33 | } else { 34 | if ($this->modx->getCount($this->classKey, ['name' => $name]) && ($this->object->name != $name)) { 35 | $this->addFieldError('name', $this->modx->lexicon('tagger.err.group_name_ae')); 36 | } 37 | } 38 | 39 | $fieldType = $this->getProperty('field_type'); 40 | $showAutotag = (int)$this->getProperty('show_autotag', 0); 41 | 42 | if ($fieldType != 'tagger-field-tags') { 43 | $this->object->set('show_autotag', 0); 44 | } 45 | 46 | if ($showAutotag != 1) { 47 | $this->object->set('hide_input', 0); 48 | } 49 | 50 | if (!empty($alias)) { 51 | $alias = $this->object->cleanAlias($alias); 52 | if ($this->modx->getCount($this->classKey, ['alias' => $alias, 'id:!=' => $this->object->id]) > 0) { 53 | $this->addFieldError('alias', $this->modx->lexicon('tagger.err.group_alias_ae')); 54 | } else { 55 | $this->object->set('alias', $alias); 56 | } 57 | } 58 | 59 | if (!(($this->object->show_autotag == 1) && ($this->object->hide_input == 1) 60 | && ($this->object->tag_limit == 1)) 61 | ) { 62 | $this->object->set('as_radio', 0); 63 | } 64 | 65 | return parent::beforeSave(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Tag/Update.php: -------------------------------------------------------------------------------- 1 | getProperty('tag'); 29 | $group = $this->getProperty('group'); 30 | $alias = $this->getProperty('alias'); 31 | 32 | if (empty($name) || empty($group)) { 33 | if (empty($group)) { 34 | $this->addFieldError('group', $this->modx->lexicon('tagger.err.group_name_ns')); 35 | } 36 | 37 | if (empty($name)) { 38 | $this->addFieldError('tag', $this->modx->lexicon('tagger.err.tag_name_ns')); 39 | } 40 | } else { 41 | if ($this->object->group != $group) { 42 | $this->addFieldError('group', $this->modx->lexicon('tagger.err.tag_group_changed')); 43 | } 44 | 45 | if ($this->modx->getCount( 46 | $this->classKey, 47 | ['tag' => $name, 'group' => $group, 'id:!=' => $this->object->id] 48 | ) > 0 49 | ) { 50 | $this->addFieldError('tag', $this->modx->lexicon('tagger.err.tag_name_ae')); 51 | } 52 | } 53 | 54 | if (!empty($alias)) { 55 | $alias = $this->object->cleanAlias($alias); 56 | if ($this->modx->getCount( 57 | $this->classKey, 58 | ['alias' => $alias, 'group' => $group, 'id:!=' => $this->object->id] 59 | ) > 0 60 | ) { 61 | $this->addFieldError('alias', $this->modx->lexicon('tagger.err.tag_alias_ae')); 62 | } else { 63 | $this->object->set('alias', $alias); 64 | } 65 | } 66 | 67 | return parent::beforeSave(); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /core/components/tagger/src/Model/TaggerTag.php: -------------------------------------------------------------------------------- 1 | alias == '') { 26 | $this->set('alias', $this->generateUniqueAlias($this->tag)); 27 | } 28 | 29 | if ($this->rank == '') { 30 | $c = $this->xpdo->newQuery(TaggerTag::class); 31 | $c->sortby('rank', 'desc'); 32 | $c->where( 33 | [ 34 | 'group' => $this->group, 35 | ] 36 | ); 37 | 38 | /** @var TaggerTag $last */ 39 | $last = $this->xpdo->getObject(TaggerTag::class, $c); 40 | 41 | $this->set('rank', $last->rank + 1); 42 | } 43 | 44 | if (empty($this->label)) { 45 | $this->label = $this->tag; 46 | } 47 | 48 | return parent:: save($cacheFlag); 49 | } 50 | 51 | public function generateUniqueAlias($tag) 52 | { 53 | $alias = $this->cleanAlias($tag); 54 | 55 | $tag = $this->xpdo->getObject(TaggerTag::class, ['alias' => $alias, 'group' => $this->group, 'id:!=' => $this->id]); 56 | $i = 1; 57 | $newAlias = $alias; 58 | 59 | while ($tag) { 60 | $newAlias = $alias . '-' . $i; 61 | $tag = $this->xpdo->getObject( 62 | TaggerTag::class, 63 | ['alias' => $newAlias, 'group' => $this->group, 'id:!=' => $this->id] 64 | ); 65 | } 66 | 67 | return $newAlias; 68 | } 69 | 70 | public function cleanAlias($tag) 71 | { 72 | $tag = str_replace('/', '-', $tag); 73 | 74 | $removeAccents = (int)$this->xpdo->getOption('tagger.remove_accents_tag', [], 1); 75 | if ($removeAccents == 1) { 76 | $tag = iconv('UTF-8', 'ASCII//TRANSLIT', $tag); 77 | } 78 | 79 | return modResource::filterPathSegment($this->xpdo, $tag); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /core/components/tagger/src/Events/OnResourceDuplicate.php: -------------------------------------------------------------------------------- 1 | scriptProperties['oldResource']; 15 | 16 | /** @var modResource $newResource */ 17 | $newResource = $this->scriptProperties['newResource']; 18 | 19 | /** @var TaggerTagResource[] $oldRelations */ 20 | $oldRelations = $this->modx->getIterator(TaggerTagResource::class, ['resource' => $oldResource->id]); 21 | 22 | foreach ($oldRelations as $oldRelation) { 23 | /** @var TaggerTagResource $newRelation */ 24 | $newRelation = $this->modx->newObject(TaggerTagResource::class); 25 | $newRelation->set('resource', $newResource->id); 26 | $newRelation->set('tag', $oldRelation->tag); 27 | $newRelation->save(); 28 | } 29 | 30 | $this->duplicateChildren($oldResource->Children, $newResource->Children); 31 | } 32 | 33 | /** 34 | * @param modResource[] $oldChildren 35 | * @param modResource[] $newChildren 36 | */ 37 | private function duplicateChildren($oldChildren, $newChildren) 38 | { 39 | if (empty($oldChildren) || empty($newChildren)) { 40 | return; 41 | } 42 | 43 | if (count($oldChildren) != count($newChildren)) { 44 | return; 45 | } 46 | 47 | $oldNew = array_combine(array_keys($oldChildren), array_keys($newChildren)); 48 | 49 | foreach ($oldChildren as $key => $oldChild) { 50 | /** @var TaggerTagResource[] $oldRelations */ 51 | $oldRelations = $this->modx->getIterator(TaggerTagResource::class, ['resource' => $oldChild->id]); 52 | 53 | foreach ($oldRelations as $oldRelation) { 54 | $newRelation = $this->modx->newObject(TaggerTagResource::class); 55 | $newRelation->set('resource', $newChildren[$oldNew[$key]]->id); 56 | $newRelation->set('tag', $oldRelation->tag); 57 | $newRelation->save(); 58 | } 59 | 60 | $this->duplicateChildren($oldChild->Children, $newChildren[$oldNew[$key]]->Children); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /core/components/tagger/src/Model/TaggerGroup.php: -------------------------------------------------------------------------------- 1 | alias == '') { 40 | $this->set('alias', $this->generateUniqueAlias($this->name)); 41 | } 42 | 43 | if (!in_array($this->sort_field, ['alias', 'rank'])) { 44 | $this->set('sort_field', 'alias'); 45 | } 46 | 47 | if (!in_array($this->sort_dir, ['asc', 'desc'])) { 48 | $this->set('sort_dir', 'asc'); 49 | } 50 | 51 | return parent:: save($cacheFlag); 52 | } 53 | 54 | public function generateUniqueAlias($name) 55 | { 56 | $alias = $this->cleanAlias($name); 57 | 58 | $group = $this->xpdo->getObject(TaggerGroup::class, ['alias' => $alias, 'id:!=' => $this->id]); 59 | $i = 1; 60 | $newAlias = $alias; 61 | 62 | while ($group) { 63 | $newAlias = $alias . '-' . $i; 64 | $group = $this->xpdo->getObject(TaggerGroup::class, ['alias' => $newAlias, 'id:!=' => $this->id]); 65 | } 66 | 67 | return $newAlias; 68 | } 69 | 70 | public function cleanAlias($name) 71 | { 72 | $name = str_replace('/', '-', $name); 73 | 74 | $removeAccents = (int)$this->xpdo->getOption('tagger.remove_accents_group', [], 1); 75 | if ($removeAccents == 1) { 76 | $name = iconv('UTF-8', 'ASCII//TRANSLIT', $name); 77 | } 78 | 79 | return modResource::filterPathSegment($this->xpdo, $name); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Group/Create.php: -------------------------------------------------------------------------------- 1 | getProperty('field_type'); 29 | $showAutotag = (int)$this->getProperty('show_autotag', 0); 30 | 31 | if ($fieldType != 'tagger-field-tags') { 32 | $this->setProperty('show_autotag', 0); 33 | } 34 | 35 | if ($showAutotag != 1) { 36 | $this->setProperty('hide_input', 0); 37 | } 38 | 39 | $c = $this->modx->newQuery(TaggerGroup::class); 40 | $c->sortby('position', 'DESC'); 41 | $c->limit(1); 42 | 43 | /** @var TaggerGroup $group */ 44 | $group = $this->modx->getObject(TaggerGroup::class, $c); 45 | 46 | if ($group) { 47 | $this->setProperty('position', $group->position + 1); 48 | } else { 49 | $this->setProperty('position', 0); 50 | } 51 | 52 | return parent::beforeSet(); 53 | } 54 | 55 | public function beforeSave() 56 | { 57 | $name = $this->getProperty('name'); 58 | $alias = $this->getProperty('alias'); 59 | 60 | if (empty($name)) { 61 | $this->addFieldError('name', $this->modx->lexicon('tagger.err.group_name_ns')); 62 | } else { 63 | if ($this->doesAlreadyExist(['name' => $name])) { 64 | $this->addFieldError('name', $this->modx->lexicon('tagger.err.group_name_ae')); 65 | } 66 | } 67 | 68 | if (!empty($alias)) { 69 | $alias = $this->object->cleanAlias($alias); 70 | if ($this->doesAlreadyExist(['alias' => $alias])) { 71 | $this->addFieldError('alias', $this->modx->lexicon('tagger.err.group_alias_ae')); 72 | } 73 | } 74 | 75 | if (!(($this->object->show_autotag == 1) && ($this->object->hide_input == 1) 76 | && ($this->object->tag_limit == 1)) 77 | ) { 78 | $this->object->set('as_radio', 0); 79 | } 80 | 81 | return parent::beforeSave(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Tag/Merge.php: -------------------------------------------------------------------------------- 1 | validate(); 29 | if ($validate !== true) { 30 | return $this->failure($validate); 31 | } 32 | 33 | $toUpdate = array_shift($this->tags); 34 | $name = $this->getProperty('name', ''); 35 | 36 | foreach ($this->tags as $tag) { 37 | $tagResources = $this->modx->getCollection(TaggerTagResource::class, ['tag' => $tag]); 38 | 39 | /** @var TaggerTagResource $tagResource */ 40 | foreach ($tagResources as $tagResource) { 41 | $newRelation = $this->modx->newObject(TaggerTagResource::class); 42 | $newRelation->set('tag', $toUpdate); 43 | $newRelation->set('resource', $tagResource->resource); 44 | 45 | $tagResource->remove(); 46 | $newRelation->save(); 47 | } 48 | 49 | $tagObject = $this->modx->getObject($this->classKey, $tag); 50 | $tagObject->remove(); 51 | } 52 | 53 | /** @var TaggerTag $toUpdate */ 54 | $toUpdate = $this->modx->getObject($this->classKey, $toUpdate); 55 | $toUpdate->set('tag', $name); 56 | $toUpdate->save(); 57 | 58 | return $this->success(); 59 | } 60 | 61 | public function validate() 62 | { 63 | $tags = $this->getProperty('tags', null); 64 | $tags = \Tagger\Utils::explodeAndClean($tags); 65 | 66 | if (empty($tags)) { 67 | return $this->modx->lexicon('tagger.err.tags_ns'); 68 | } 69 | 70 | $name = $this->getProperty('name', ''); 71 | if (empty($name)) { 72 | $this->addFieldError('name', $this->modx->lexicon('tagger.err.tag_name_ns')); 73 | return false; 74 | } 75 | 76 | $exists = $this->modx->getCount($this->classKey, ['tag' => $name, 'id:NOT IN' => $tags]); 77 | if ($exists > 0) { 78 | $this->addFieldError('name', $this->modx->lexicon('tagger.err.tag_name_ae')); 79 | return false; 80 | } 81 | 82 | $this->tags = $tags; 83 | 84 | return true; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /_build/gpm_resolvers/gpm.resolve.element_property_set.php: -------------------------------------------------------------------------------- 1 | xpdo; 19 | if ($options[xPDOTransport::PACKAGE_ACTION] === xPDOTransport::ACTION_UNINSTALL) return true; 20 | 21 | $propertySetsCache = []; 22 | 23 | $elementClasses = [ 24 | 'snippets' => 'MODX\\Revolution\\modSnippet', 25 | 'chunks' => 'MODX\\Revolution\\modChunk', 26 | 'templates' => 'MODX\\Revolution\\modTemplate', 27 | 'plugins' => 'MODX\\Revolution\\modPlugin', 28 | ]; 29 | 30 | foreach ($elementClasses as $type => $elementClass) { 31 | if (isset($fileMeta[$type]) && is_array($fileMeta[$type])) { 32 | foreach ($fileMeta[$type] as $elementName => $propertySets) { 33 | /** @var \MODX\Revolution\modElement $element */ 34 | $element = $modx->getObject($elementClass, ['name' => $elementName]); 35 | if (!$element) continue; 36 | 37 | if (empty($propertySets)) { 38 | $modx->removeCollection(\MODX\Revolution\modElementPropertySet::class, ['element' => $element->id, 'element_class' => $elementClass]); 39 | continue; 40 | } 41 | 42 | if (!is_array($propertySets)) continue; 43 | 44 | foreach ($propertySets as $propertySetName) { 45 | if (!isset($propertySetsCache[$propertySetName])) { 46 | /** @var \MODX\Revolution\modPropertySet $propertySet */ 47 | $propertySet = $modx->getObject(\MODX\Revolution\modPropertySet::class, ['name' => $propertySetName]); 48 | if (!$propertySet) continue; 49 | 50 | $propertySetsCache[$propertySetName] = $propertySet->id; 51 | } 52 | 53 | $elementPropertySet = $modx->getObject(\MODX\Revolution\modElementPropertySet::class, ['element' => $element->id, 'element_class' => $elementClass, 'property_set' => $propertySetsCache[$propertySetName]]); 54 | if ($elementPropertySet) continue; 55 | 56 | $elementPropertySet = $modx->newObject(\MODX\Revolution\modElementPropertySet::class); 57 | $elementPropertySet->set('element', $element->id); 58 | $elementPropertySet->set('element_class', $elementClass); 59 | $elementPropertySet->set('property_set', $propertySetsCache[$propertySetName]); 60 | $elementPropertySet->save(); 61 | } 62 | } 63 | } 64 | } 65 | 66 | return true; 67 | -------------------------------------------------------------------------------- /core/components/tagger/src/Model/mysql/TaggerTagResource.php: -------------------------------------------------------------------------------- 1 | 'Tagger\\Model\\', 11 | 'version' => '3.0', 12 | 'table' => 'tagger_tag_resources', 13 | 'extends' => 'xPDO\\Om\\xPDOObject', 14 | 'tableMeta' => 15 | array ( 16 | 'engine' => 'InnoDB', 17 | ), 18 | 'fields' => 19 | array ( 20 | 'tag' => NULL, 21 | 'resource' => NULL, 22 | ), 23 | 'fieldMeta' => 24 | array ( 25 | 'tag' => 26 | array ( 27 | 'dbtype' => 'integer', 28 | 'attributes' => 'unsigned', 29 | 'precision' => '10', 30 | 'phptype' => 'int', 31 | 'null' => false, 32 | 'index' => 'pk', 33 | ), 34 | 'resource' => 35 | array ( 36 | 'dbtype' => 'integer', 37 | 'attributes' => 'unsigned', 38 | 'precision' => '10', 39 | 'phptype' => 'int', 40 | 'null' => false, 41 | 'index' => 'pk', 42 | ), 43 | ), 44 | 'indexes' => 45 | array ( 46 | 'PRIMARY' => 47 | array ( 48 | 'alias' => 'PRIMARY', 49 | 'primary' => true, 50 | 'unique' => true, 51 | 'type' => 'BTREE', 52 | 'columns' => 53 | array ( 54 | 'tag' => 55 | array ( 56 | 'length' => '', 57 | 'collation' => 'A', 58 | 'null' => false, 59 | ), 60 | 'resource' => 61 | array ( 62 | 'length' => '', 63 | 'collation' => 'A', 64 | 'null' => false, 65 | ), 66 | ), 67 | ), 68 | ), 69 | 'aggregates' => 70 | array ( 71 | 'Tag' => 72 | array ( 73 | 'class' => 'Tagger\\Model\\TaggerTag', 74 | 'local' => 'tag', 75 | 'foreign' => 'id', 76 | 'cardinality' => 'one', 77 | 'owner' => 'foreign', 78 | ), 79 | 'Resource' => 80 | array ( 81 | 'class' => 'MODX\\Revolution\\modResource', 82 | 'local' => 'resource', 83 | 'foreign' => 'id', 84 | 'cardinality' => 'one', 85 | 'owner' => 'foreign', 86 | ), 87 | ), 88 | ); 89 | 90 | } 91 | -------------------------------------------------------------------------------- /core/components/tagger/lexicon/en/properties.inc.php: -------------------------------------------------------------------------------- 1 | modx->getOption('mode', $this->scriptProperties, 'upd'); 15 | 16 | $this->modx->controller->addLexiconTopic('tagger:default'); 17 | $this->modx->regClientCSS($this->tagger->getOption('cssUrl') . 'tagfield.css'); 18 | 19 | $this->modx->regClientStartupScript($this->tagger->getOption('jsUrl') . 'mgr/tagger.js'); 20 | $this->modx->regClientStartupScript($this->tagger->getOption('jsUrl') . 'mgr/extras/tagger.tagfield.js'); 21 | $this->modx->regClientStartupScript($this->tagger->getOption('jsUrl') . 'mgr/extras/tagger.combo.js'); 22 | 23 | $c = $this->modx->newQuery(TaggerGroup::class); 24 | $c->sortby('position'); 25 | 26 | /** @var TaggerGroup[] $groups */ 27 | $groups = $this->modx->getIterator(TaggerGroup::class, $c); 28 | $groupsArray = []; 29 | foreach ($groups as $group) { 30 | $showForTemplates = $group->show_for_templates; 31 | $showForTemplates = \Tagger\Utils::explodeAndClean($showForTemplates, ',', true); 32 | 33 | $showForContexts = $group->show_for_contexts; 34 | $showForContexts = \Tagger\Utils::explodeAndClean($showForContexts); 35 | 36 | $groupsArray[] = array_merge( 37 | $group->toArray(), 38 | [ 39 | 'show_for_templates' => array_values($showForTemplates), 40 | 'show_for_contexts' => array_values($showForContexts), 41 | ] 42 | ); 43 | } 44 | 45 | $tagsArray = []; 46 | 47 | if ($mode == 'upd') { 48 | $c = $this->modx->newQuery(TaggerTagResource::class); 49 | $c->leftJoin(TaggerTag::class, 'Tag'); 50 | $c->where(['resource' => intval($_GET['id'])]); 51 | $c->sortby('Tag.alias', 'ASC'); 52 | $c->select($this->modx->getSelectColumns(TaggerTagResource::class, 'TaggerTagResource', '', ['resource'])); 53 | $c->select($this->modx->getSelectColumns(TaggerTag::class, 'Tag', '', ['tag', 'group'])); 54 | 55 | $c->prepare(); 56 | $c->stmt->execute(); 57 | while ($relatedTag = $c->stmt->fetch(\PDO::FETCH_ASSOC)) { 58 | if (!isset($tagsArray['tagger-' . $relatedTag['group']])) { 59 | $tagsArray['tagger-' . $relatedTag['group']] = $relatedTag['tag']; 60 | } else { 61 | $tagsArray['tagger-' . $relatedTag['group']] .= ',' . $relatedTag['tag']; 62 | } 63 | } 64 | } 65 | 66 | $this->modx->regClientStartupHTMLBlock(' 67 | 72 | '); 73 | 74 | $this->modx->regClientStartupScript($this->tagger->getOption('jsUrl') . 'mgr/inject/tab.js'); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tagger 2 | ====== 3 | *Tags, Categories, and More for MODX!* 4 | 5 | A robust and performant tag management system. Summary of the many, many features: 6 | 7 | 1. Tested with up to a million tags 8 | 2. Paginated drop-down and type-ahead for easy tag input 9 | 3. Combo-box or tag-field input types 10 | 4. Optionally remove unused tags from the database automatically 11 | 5. Optionally restrict tag creation to the CMP, versus on input 12 | 6. Optionally use Auto-Tag cloud for input 13 | 14 | Display and list: all tags, tags from specified group(s), omit unused tags, Resources with a given tag, etc. Supplies getResources with a &where condition, so that all the templating and sorting abilities of getResources are at your fingertips. 15 | 16 | ## Installation 17 | 18 | Install via Package Management, or download the package from the [MODX Extras repository](http://modx.com/extras/) 19 | 20 | ## Basic Usage 21 | 22 | ### TaggerGetTags 23 | 24 | This Snippet allows you to list tags for resource(s), group(s) and all tags 25 | 26 | **PROPERTIES:** 27 | 28 | &resources Comma separated list of resources for which will be listed Tags 29 | 30 | &groups Comma separated list of Tagger Groups for which will be listed Tags 31 | 32 | &rowTpl Name of a chunk that will be used for each Tag. If no chunk is given, array with available placeholders will be rendered 33 | 34 | &outTpl Name of a chunk that will be used for wrapping all tags. If no chunk is given, tags will be rendered without a wrapper 35 | 36 | &separator String separator, that will be used for separating Tags 37 | 38 | &target An ID of a resource that will be used for generating URI for a Tag. If no ID is given, current Resource ID will be used 39 | 40 | &showUnused If set to 1, Tags that are not assigned to any Resource will be included to the output as well 41 | 42 | **OUTPUT PLACEHOLDERS AND EXAMPLE VALUES:** 43 | 44 | [[+id]] => 1 45 | 46 | [[+tag]] => News 47 | 48 | [[+group]] => 3 49 | 50 | [[+group_id]] => 3 51 | 52 | [[+group_name]] => Media Type 53 | 54 | [[+group_field_type]] => tagger-combo-tag 55 | 56 | [[+group_allow_new]] => 0 57 | 58 | [[+group_remove_unused]] => 0 59 | 60 | [[+group_allow_blank]] => 1 61 | 62 | [[+group_allow_type]] => 0 63 | 64 | [[+group_show_autotag]] => 0 65 | 66 | [[+group_show_for_templates]] => 21 67 | 68 | [[+cnt]] => 1 69 | 70 | [[+uri]] 71 | 72 | **EXAMPLE USAGE:** 73 | 74 | ```[[TaggerGetTags? &showUnused=`1`]]``` 75 | 76 | ```[[TaggerGetTags? &groups=`1,3` &rowTpl=`tag_links_tpl`]]``` 77 | 78 | ### TaggerGetResourcesWhere 79 | 80 | This snippet generate SQL Query that can be used in WHERE condition in getResources snippet 81 | 82 | **PROPERTIES:** 83 | 84 | &tags Comma separated list of Tags for which will be generated a Resource query. By default Tags from GET param will be loaded 85 | 86 | &groups Comma separated list of Tagger Groups. Only from those groups will Tags be allowed 87 | 88 | &where Original getResources where property. If you used where property in your current getResources call, move it here 89 | 90 | **EXAMPLE USAGE:** 91 | 92 | ```[[!getResources? &where=`[[!TaggerGetResourcesWhere? &tags=`Books,Vehicles` &where=`{"isfolder": 0}`]]`]]``` 93 | 94 | ## Documentation 95 | 96 | Learn more about Tagger in the [Official Documentation](http://rtfm.modx.com/extras/revo/tagger). 97 | 98 | ## License 99 | 100 | Tagger is GPL2. For the full copyright and license information, please view the license.txt file that was distributed with this source code, under /core/components/tagger/docs/. 101 | -------------------------------------------------------------------------------- /assets/components/tagger/js/mgr/extras/griddraganddrop.js: -------------------------------------------------------------------------------- 1 | Ext.ux.dd.GridReorderDropTarget = function(grid, config) { 2 | this.target = new Ext.dd.DropTarget(grid.getEl(), { 3 | ddGroup: grid.ddGroup || 'GridDD' 4 | ,grid: grid 5 | ,sortCol: 'position' 6 | ,gridDropTarget: this 7 | ,notifyDrop: function(dd, e, data){ 8 | if (data.grid.store.sortInfo && data.grid.store.sortInfo.field != this.sortCol) { 9 | return false; 10 | } 11 | 12 | var search = this.grid.getStore().baseParams.query; 13 | if (search && search != '') { 14 | return this.dropNotAllowed; 15 | } 16 | 17 | // determine the row 18 | var t = Ext.lib.Event.getTarget(e); 19 | var rindex = this.grid.getView().findRowIndex(t); 20 | if (rindex === false) return false; 21 | if (rindex == data.rowIndex) return false; 22 | 23 | var menuIndexes = {}; 24 | menuIndexes.oldIndex = this.grid.store.data.items[data.rowIndex].data[this.sortCol]; 25 | menuIndexes.newIndex = this.grid.store.data.items[rindex].data[this.sortCol]; 26 | 27 | // fire the before move/copy event 28 | if (this.gridDropTarget.fireEvent(this.copy?'beforerowcopy':'beforerowmove', this.gridDropTarget, menuIndexes.oldIndex, menuIndexes.newIndex, data.selections) === false) return false; 29 | 30 | // update the store 31 | var ds = this.grid.getStore(); 32 | if (!this.copy) { 33 | for(i = 0; i < data.selections.length; i++) { 34 | ds.remove(ds.getById(data.selections[i].id)); 35 | } 36 | } 37 | ds.insert(rindex,data.selections); 38 | 39 | // re-select the row(s) 40 | var sm = this.grid.getSelectionModel(); 41 | if (sm) sm.selectRecords(data.selections); 42 | 43 | // fire the after move/copy event 44 | this.gridDropTarget.fireEvent(this.copy?'afterrowcopy':'afterrowmove', this.gridDropTarget, menuIndexes.oldIndex, menuIndexes.newIndex, data.selections); 45 | 46 | return true; 47 | } 48 | ,notifyOver: function(dd, e, data) { 49 | this.grid.getView().dragZone.ddel.innerHTML = this.grid.getDragDropText(); 50 | this.grid.getView().dragZone.proxy.update(this.grid.getView().dragZone.ddel); 51 | 52 | if (data.grid.store.sortInfo && data.grid.store.sortInfo.field != this.sortCol) { 53 | return this.dropNotAllowed; 54 | } 55 | 56 | var search = this.grid.getStore().baseParams.query; 57 | if (search && search != '') { 58 | return this.dropNotAllowed; 59 | } 60 | 61 | var t = Ext.lib.Event.getTarget(e); 62 | var rindex = this.grid.getView().findRowIndex(t); 63 | if (rindex == data.rowIndex) rindex = false; 64 | 65 | return (rindex === false)? this.dropNotAllowed : this.dropAllowed; 66 | } 67 | }); 68 | if (config) { 69 | Ext.apply(this.target, config); 70 | if (config.listeners) Ext.apply(this,{listeners: config.listeners}); 71 | } 72 | 73 | this.addEvents({ 74 | "beforerowmove": true 75 | ,"afterrowmove": true 76 | ,"beforerowcopy": true 77 | ,"afterrowcopy": true 78 | }); 79 | 80 | Ext.ux.dd.GridReorderDropTarget.superclass.constructor.call(this); 81 | }; 82 | 83 | Ext.extend(Ext.ux.dd.GridReorderDropTarget, Ext.util.Observable, { 84 | getTarget: function() { 85 | return this.target; 86 | } 87 | ,getGrid: function() { 88 | return this.target.grid; 89 | } 90 | ,getCopy: function() { 91 | return this.target.copy?true:false; 92 | } 93 | ,setCopy: function(b) { 94 | this.target.copy = b?true:false; 95 | } 96 | }); -------------------------------------------------------------------------------- /core/components/tagger/src/TaggerGateway.php: -------------------------------------------------------------------------------- 1 | modx =& $modx; 26 | } 27 | 28 | public function init($scriptProperties) 29 | { 30 | $this->scriptProperties = $scriptProperties; 31 | } 32 | 33 | public function handleRequest() 34 | { 35 | $requestParamAlias = $this->modx->getOption('request_param_alias', null, 'q'); 36 | if (!isset($_REQUEST[$requestParamAlias])) { 37 | return true; 38 | } 39 | 40 | $this->pieces = explode('/', trim($_REQUEST[$requestParamAlias], ' ')); 41 | $pieces = array_flip($this->pieces); 42 | 43 | $c = $this->modx->newQuery(TaggerGroup::class); 44 | $c->select($this->modx->getSelectColumns(TaggerGroup::class, '', '', ['alias'])); 45 | $c->prepare(); 46 | $c->stmt->execute(); 47 | $this->groups = $c->stmt->fetchAll(\PDO::FETCH_COLUMN, 0); 48 | 49 | foreach ($this->groups as $group) { 50 | if (isset($pieces[$group])) { 51 | $this->tags[$group] = $pieces[$group]; 52 | } 53 | } 54 | 55 | if (count($this->tags) == 0) { 56 | return false; 57 | } 58 | 59 | asort($this->tags); 60 | 61 | if ($this->pieces[count($this->pieces) - 1] != '') { 62 | $this->modx->sendRedirect( 63 | MODX_SITE_URL . implode('/', $this->pieces) . '/', 64 | ['responseCode' => $_SERVER['SERVER_PROTOCOL'] . ' 301 Moved Permanently'] 65 | ); 66 | } 67 | if (count($this->pieces) == 0 || (count($this->pieces) == 1 && $this->pieces[0] == '')) { 68 | return false; 69 | } 70 | 71 | $this->processRequest(); 72 | 73 | return true; 74 | } 75 | 76 | private function processRequest() 77 | { 78 | $prev = ['group' => '', 'id' => -1]; 79 | $pieces = []; 80 | foreach ($this->tags as $group => $id) { 81 | if ($prev['id'] == -1) { 82 | $pieces = array_slice($this->pieces, 0, $id); 83 | $prev['id'] = $id; 84 | $prev['group'] = $group; 85 | continue; 86 | } 87 | 88 | $_GET[$prev['group']] = Utils::cleanAndImplode( 89 | array_slice($this->pieces, $prev['id'] + 1, $id - $prev['id'] - 1) 90 | ); 91 | $prev['id'] = $id; 92 | $prev['group'] = $group; 93 | } 94 | 95 | $_GET[$prev['group']] = Utils::cleanAndImplode(array_slice($this->pieces, $prev['id'] + 1)); 96 | 97 | $q = implode('/', $pieces); 98 | if ($q == '') { 99 | $siteStart = $this->modx->getObject(modResource::class, $this->modx->getOption('site_start')); 100 | $q = $siteStart->alias; 101 | } 102 | 103 | $containerSuffix = trim($this->modx->getOption('container_suffix', null, '')); 104 | $found = $this->modx->findResource($q); 105 | 106 | if ($found === false && !empty ($containerSuffix)) { 107 | $suffixLen = strlen($containerSuffix); 108 | $identifierLen = strlen($q); 109 | if (substr($q, $identifierLen - $suffixLen) === $containerSuffix) { 110 | $identifier = substr($q, 0, $identifierLen - $suffixLen); 111 | $found = $this->modx->findResource($identifier); 112 | } else { 113 | $identifier = "{$q}{$containerSuffix}"; 114 | $found = $this->modx->findResource($identifier); 115 | } 116 | } 117 | 118 | if ($found) { 119 | $this->modx->sendForward($found); 120 | } 121 | 122 | return true; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /assets/components/tagger/css/tagfield.css: -------------------------------------------------------------------------------- 1 | .tagger-field-tags .tagger-field-wrapper { 2 | margin-bottom:8px; 3 | padding-right: 65px; 4 | position:relative; 5 | } 6 | 7 | .tagger-field-tags .tagger-field-wrapper:before, .tagger-field-tags .tagger-field-wrapper:after { 8 | content: "\0020"; 9 | display: block; 10 | height: 0; 11 | overflow: hidden; 12 | } 13 | 14 | .tagger-field-tags .tagger-field-wrapper:after { clear: both; } 15 | 16 | .tagger-field-tags { 17 | width:auto !important; 18 | } 19 | 20 | .tagger-field-tags .x-form-field-wrap { 21 | background:none; 22 | } 23 | 24 | .tagger-field-tags input.x-form-text.x-form-field { 25 | width: 96% !important; 26 | height:100% !important; 27 | -webkit-box-sizing:border-box; 28 | -moz-box-sizing:border-box; 29 | box-sizing:border-box; 30 | font-weight:normal; 31 | } 32 | 33 | .tagger-field-tags button { 34 | margin:0 0 0 4px; 35 | padding:0 8px; 36 | height:32px; 37 | float:left; 38 | color:#53595f; 39 | font:bold 11px tahoma, verdana, helvetica, sans-serif; 40 | border-color:#D5D5D5; 41 | position:absolute; 42 | top:0; 43 | right:0; 44 | } 45 | 46 | .tagger-field-tags .x-btn:focus { 47 | border-color:#668f16; 48 | } 49 | 50 | .tagger-field-tags .x-form-field-wrap { 51 | width: 100% !important; 52 | min-width:120px; 53 | float:left; 54 | } 55 | 56 | .tagger-field-tags .inserted-tags { 57 | margin-top:3px; 58 | } 59 | 60 | .tagger-field-tags .inserted-tags li { 61 | float:left; 62 | margin:0 4px 4px 0; 63 | } 64 | 65 | .tagger-field-tags .inserted-tags li:last-of-type { 66 | margin-right:0; 67 | } 68 | 69 | .tagger-autotag-item { 70 | 71 | } 72 | 73 | .tagger-wrapper-hidden { 74 | display: none; 75 | } 76 | 77 | .tagger-header { 78 | -webkit-box-shadow: 0 0 5px 0 rgba(0,0,0,.1); 79 | box-shadow: 0 0 5px 0 rgba(0,0,0,.1); 80 | } 81 | 82 | .tagger-header .x-panel-header { 83 | padding: 15px !important; 84 | } 85 | 86 | .inserted-tags.modx-tag-list.x-superboxselect li.tagger-autotag-item.modx-tag-opt { 87 | background-color: #fff; 88 | background-repeat: no-repeat; 89 | border: thin solid #e4e4e4; 90 | border-radius: 3px; 91 | color: #555; 92 | cursor: pointer; 93 | display: inline-block; 94 | line-height: 1; 95 | padding: 5px 10px !important; 96 | position: relative; 97 | text-decoration: none; 98 | zoom: 1; 99 | margin-bottom: 3px; 100 | margin-left: 0px; 101 | font-size: 12px; 102 | } 103 | 104 | .inserted-tags.modx-tag-list.x-superboxselect li.tagger-autotag-item.modx-tag-opt:hover { 105 | background-color: #2d86b7; 106 | color: #fff; 107 | } 108 | 109 | .inserted-tags.modx-tag-list.x-superboxselect li.tagger-autotag-item.modx-tag-opt:before { 110 | display: none; 111 | } 112 | 113 | .inserted-tags.modx-tag-list.x-superboxselect li.tagger-autotag-item.modx-tag-opt:after { 114 | display: none; 115 | } 116 | 117 | .inserted-tags.modx-tag-list.x-superboxselect li.tagger-autotag-item.modx-tag-opt.modx-tag-checked { 118 | background-color: #2d86b7; 119 | color: #fff; 120 | border: thin solid #2d86b7; 121 | } 122 | 123 | .inserted-tags.modx-tag-list.x-superboxselect li.x-superboxselect-item { 124 | background-color: #fff; 125 | background-repeat: no-repeat; 126 | border-radius: 3px; 127 | border: thin solid #e4e4e4; 128 | -webkit-box-shadow: none; 129 | box-shadow: none; 130 | color: #555; 131 | cursor: pointer; 132 | display: inline-block; 133 | line-height: 1; 134 | padding: 5px 24px 5px 10px !important; 135 | position: relative; 136 | text-decoration: none; 137 | zoom: 1; 138 | margin-bottom: 3px; 139 | margin-left: 0px; 140 | font-size: 12px; 141 | } 142 | 143 | .inserted-tags.modx-tag-list.x-superboxselect li.x-superboxselect-item span.x-superboxselect-item-close { 144 | right: 2px; 145 | border-left: thin solid #e4e4e4; 146 | padding-left: 1px; 147 | } 148 | 149 | .inserted-tags.modx-tag-list.x-superboxselect li.x-superboxselect-item:hover { 150 | background-color: #2d86b7; 151 | color: #fff; 152 | } -------------------------------------------------------------------------------- /_build/gpm_scripts/gpm.script.sync_tables.php: -------------------------------------------------------------------------------- 1 | getTableName($table); 21 | $tableName = str_replace('`', '', $tableName); 22 | $dbname = $modx->getOption('dbname'); 23 | 24 | $c = $modx->prepare("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = :dbName AND table_name = :tableName"); 25 | $c->bindParam(':dbName', $dbname); 26 | $c->bindParam(':tableName', $tableName); 27 | $c->execute(); 28 | 29 | $unusedColumns = $c->fetchAll(PDO::FETCH_COLUMN, 0); 30 | $unusedColumns = array_flip($unusedColumns); 31 | 32 | $meta = $modx->getFieldMeta($table); 33 | $columns = array_keys($meta); 34 | 35 | $m = $modx->getManager(); 36 | 37 | foreach ($columns as $column) { 38 | if (isset($unusedColumns[$column])) { 39 | $m->alterField($table, $column); 40 | $modx->log(modX::LOG_LEVEL_INFO, ' -- altered column: ' . $column); 41 | unset($unusedColumns[$column]); 42 | } else { 43 | $m->addField($table, $column); 44 | $modx->log(modX::LOG_LEVEL_INFO, ' -- added column: ' . $column); 45 | } 46 | } 47 | 48 | foreach ($unusedColumns as $column => $v) { 49 | $m->removeField($table, $column); 50 | $modx->log(modX::LOG_LEVEL_INFO, ' -- removed column: ' . $column); 51 | } 52 | } 53 | } 54 | 55 | if (!function_exists('updateTableIndexes')) { 56 | /** 57 | * @param $modx 58 | * @param string $table 59 | */ 60 | function updateTableIndexes($modx, $table) 61 | { 62 | $tableName = $modx->getTableName($table); 63 | $tableName = str_replace('`', '', $tableName); 64 | $dbname = $modx->getOption('dbname'); 65 | 66 | $c = $modx->prepare("SELECT DISTINCT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE table_schema = :dbName AND table_name = :tableName AND INDEX_NAME != 'PRIMARY'"); 67 | $c->bindParam(':dbName', $dbname); 68 | $c->bindParam(':tableName', $tableName); 69 | $c->execute(); 70 | 71 | $oldIndexes = $c->fetchAll(PDO::FETCH_COLUMN, 0); 72 | 73 | $m = $modx->getManager(); 74 | 75 | foreach ($oldIndexes as $oldIndex) { 76 | $m->removeIndex($table, $oldIndex); 77 | $modx->log(modX::LOG_LEVEL_INFO, ' -- removed index: ' . $oldIndex); 78 | } 79 | 80 | $meta = $modx->getIndexMeta($table); 81 | $indexes = array_keys($meta); 82 | 83 | foreach ($indexes as $index) { 84 | if ($index == 'PRIMARY') continue; 85 | $m->addIndex($table, $index); 86 | $modx->log(modX::LOG_LEVEL_INFO, ' -- added index: ' . $index); 87 | } 88 | } 89 | } 90 | 91 | if (!function_exists('alterTable')) { 92 | /** 93 | * @param $modx 94 | * @param string $table 95 | */ 96 | function alterTable($modx, $table) 97 | { 98 | $modx->log(modX::LOG_LEVEL_INFO, ' - Updating columns'); 99 | updateTableColumns($modx, $table); 100 | 101 | $modx->log(modX::LOG_LEVEL_INFO, ' - Updating indexes'); 102 | updateTableIndexes($modx, $table); 103 | } 104 | } 105 | 106 | if ($transport->xpdo) { 107 | switch ($options[xPDOTransport::PACKAGE_ACTION]) { 108 | case xPDOTransport::ACTION_UPGRADE: 109 | $modx =& $transport->xpdo; 110 | 111 | $tables = [ 112 | \Tagger\Model\TaggerGroup::class, 113 | \Tagger\Model\TaggerTag::class, 114 | \Tagger\Model\TaggerTagResource::class, 115 | ]; 116 | 117 | foreach ($tables as $table) { 118 | $modx->log(modX::LOG_LEVEL_INFO, 'Altering table: ' . $table); 119 | alterTable($modx, $table); 120 | } 121 | 122 | break; 123 | } 124 | } 125 | return true; 126 | -------------------------------------------------------------------------------- /assets/components/tagger/css/superboxselect.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | .x-superboxselect { 3 | position: relative; 4 | height: auto !important; 5 | margin: 0px; 6 | overflow: hidden; 7 | padding: 2px; 8 | display: block; 9 | outline: none !important; 10 | } 11 | 12 | .x-superboxselect input[disabled] { 13 | background-color: transparent; 14 | } 15 | 16 | .x-superboxselect ul { 17 | overflow: hidden; 18 | cursor: text; 19 | } 20 | 21 | .x-superboxselect-display-btns { 22 | padding-right: 33px !important; 23 | } 24 | 25 | .x-superboxselect-btns { 26 | position: absolute; 27 | right: -5px; 28 | top: 0; 29 | overflow: hidden; 30 | padding: 2px; 31 | } 32 | 33 | .x-superboxselect-btns div { 34 | /*float: none;*/ 35 | width: 32px; 36 | height: 32px; 37 | /*margin-top: 4px;*/ 38 | } 39 | 40 | .x-superboxselect-btn-clear { 41 | background: url(../img/superboxselect/clear.png) no-repeat scroll left center transparent; 42 | position: relative; 43 | /*left: 50px;*/ 44 | /*top: 14px;*/ 45 | position: relative; 46 | top: -5px; 47 | width: 12px !important; 48 | left: 2px; 49 | } 50 | 51 | .x-superboxselect-btn-expand { 52 | background: url("../img/superboxselect/trigger.png") no-repeat scroll left center transparent; 53 | position: relative; 54 | /*left: 8px;*/ 55 | top: -6px; 56 | /*background: url(../img/superboxselect/expand.png) no-repeat scroll left 0px;*/ 57 | } 58 | 59 | .x-superboxselect-btn-over { 60 | background-position: center center; 61 | } 62 | 63 | .x-superboxselect-btn-clear-over{ 64 | background-position: center center; 65 | position: relative; 66 | /*left: 40px;*/ 67 | } 68 | 69 | .x-superboxselect-btn-hide { 70 | display: none; 71 | } 72 | 73 | .x-superboxselect li { 74 | float: left; 75 | margin: 1px 1px 2px 1px; 76 | padding: 0; 77 | line-height: 18px; 78 | } 79 | 80 | .x-superboxselect-stacked li { 81 | float: none !important; 82 | } 83 | 84 | .x-superboxselect-input input { 85 | border: none; 86 | outline: none; 87 | margin-top: 4px; 88 | margin-bottom: 4px; 89 | background-color: transparent; 90 | } 91 | 92 | body.ext-ie .x-superboxselect-input input { 93 | background: none; 94 | border: none; 95 | margin-top: 3px; 96 | } 97 | 98 | .x-superboxselect-item { 99 | position: relative; 100 | -moz-border-radius: 6px; 101 | -webkit-border-radius: 6px; 102 | border-radius: 6px; 103 | o-border-radius: 6px; 104 | khtml-border-radius: 6px; 105 | border: 1px solid #CAD8F3; 106 | background-color: #DEE7F8; 107 | padding: 1px 15px 1px 5px !important; 108 | } 109 | 110 | body.ext-ie7 .x-superboxselect-item { 111 | margin: 2px 1px 2px 1px; 112 | line-height: 1.2em; 113 | padding: 2px 17px 4px 5px !important; 114 | } 115 | 116 | body.ext-ie6 .x-superboxselect-item { 117 | margin: 2px 1px 2px 1px; 118 | line-height: 1.2em; 119 | padding: 2px 19px 4px 5px !important; 120 | } 121 | 122 | .x-superboxselect-item-hover { 123 | background: #BBCEF1; 124 | border: 1px solid #6D95E0; 125 | } 126 | 127 | .x-superboxselect-item-focus { 128 | border-color: #598BEC; 129 | background: #598BEC; 130 | color: #fff; 131 | } 132 | 133 | .x-superboxselect-item-close { 134 | background: url(../img/superboxselect/close.png) no-repeat scroll left 0px; 135 | border: none; 136 | cursor: default; 137 | font-size: 1px; 138 | height: 16px; 139 | padding: 0; 140 | position: absolute; 141 | right: 0px; 142 | top: 2px; 143 | width: 13px; 144 | display: block; 145 | cursor: pointer; 146 | } 147 | 148 | .x-superboxselect-item-close:hover, .x-superboxselect-item-close:active { 149 | background-position: left -12px; 150 | } 151 | 152 | .x-superboxselect-item-focus .x-superboxselect-item-close { 153 | background-position: left -24px 154 | } 155 | 156 | .x-item-disabled .x-superboxselect-item-close { 157 | background-position: left -36px 158 | } 159 | 160 | /*.x-form-field-trigger-wrap {*/ 161 | /*height: auto;*/ 162 | /*}*/ 163 | 164 | .x-superbox-auto-height { 165 | height: auto; 166 | } 167 | 168 | .ext-strict .x-form-field-trigger-wrap .x-form-text, .ext-strict .x-small-editor .x-form-field-trigger-wrap .x-form-text { 169 | height: auto !important; 170 | } 171 | 172 | .x-small-editor .x-form-field-trigger-wrap.x-superbox-oneline-wrapper .x-form-text{ 173 | padding-top: 3px !important; 174 | } -------------------------------------------------------------------------------- /assets/components/tagger/js/mgr/widgets/tag/tag.window.js: -------------------------------------------------------------------------------- 1 | tagger.window.Tag = function(config) { 2 | config = config || {}; 3 | Ext.applyIf(config,{ 4 | title: _('tagger.tag.create'), 5 | width: 475, 6 | closeAction: 'close', 7 | isUpdate: false, 8 | url: MODx.config.connector_url, 9 | action: 'Tagger\\Processors\\Tag\\Create', 10 | fields: this.getFields(config) 11 | }); 12 | tagger.window.Tag.superclass.constructor.call(this,config); 13 | }; 14 | Ext.extend(tagger.window.Tag,MODx.Window, { 15 | getFields: function(config) { 16 | return [{ 17 | xtype: 'textfield', 18 | name: 'id', 19 | anchor: '100%', 20 | hidden: true 21 | },{ 22 | xtype: 'textfield', 23 | fieldLabel: _('tagger.tag.name'), 24 | name: 'tag', 25 | anchor: '100%', 26 | allowBlank: false 27 | },{ 28 | xtype: 'textfield', 29 | fieldLabel: _('tagger.tag.label'), 30 | name: 'label', 31 | anchor: '100%', 32 | allowBlank: true 33 | },{ 34 | xtype: 'textfield', 35 | fieldLabel: _('tagger.tag.alias'), 36 | name: 'alias', 37 | anchor: '100%', 38 | allowBlank: true 39 | },{ 40 | xtype: 'tagger-combo-group', 41 | fieldLabel: _('tagger.tag.group'), 42 | name: 'group', 43 | hiddenName: 'group', 44 | anchor: '100%', 45 | allowBlank: false, 46 | readOnly: config.isUpdate, 47 | cls: (config.isUpdate == true) ? 'x-item-disabled' : '' 48 | }]; 49 | } 50 | }); 51 | Ext.reg('tagger-window-tag',tagger.window.Tag); 52 | 53 | tagger.window.AssignedResources = function(config) { 54 | config = config || {}; 55 | Ext.applyIf(config,{ 56 | title: _('tagger.tag.assigned_resources'), 57 | width: '60%', 58 | y: 40, 59 | closeAction: 'close', 60 | url: MODx.config.connector_url, 61 | tagId: 0, 62 | items: this.getFields(config), 63 | buttons: [{ 64 | text: _('cancel'), 65 | scope: this, 66 | handler: function() { 67 | this.close(); 68 | } 69 | }] 70 | }); 71 | tagger.window.AssignedResources.superclass.constructor.call(this,config); 72 | }; 73 | Ext.extend(tagger.window.AssignedResources,MODx.Window, { 74 | getFields: function(config) { 75 | return [{ 76 | xtype: 'tagger-grid-assigned-resources', 77 | baseParams: { 78 | action: 'Tagger\\Processors\\Tag\\GetAssignedResources', 79 | tagId: config.tagId 80 | }, 81 | preventRender: true, 82 | cls: 'main-wrapper' 83 | }]; 84 | } 85 | }); 86 | Ext.reg('tagger-window-assigned-resources',tagger.window.AssignedResources); 87 | 88 | tagger.window.MergeTags = function(config) { 89 | config = config || {}; 90 | Ext.applyIf(config,{ 91 | title: _('tagger.tag.merge'), 92 | width: 475, 93 | closeAction: 'close', 94 | url: MODx.config.connector_url, 95 | action: 'Tagger\\Processors\\Tag\\Create', 96 | fields: this.getFields(config) 97 | }); 98 | tagger.window.MergeTags.superclass.constructor.call(this,config); 99 | }; 100 | Ext.extend(tagger.window.MergeTags,MODx.Window, { 101 | getFields: function(config) { 102 | return [{ 103 | xtype: 'textfield', 104 | name: 'tags', 105 | anchor: '100%', 106 | hidden: true 107 | },{ 108 | html: '

Tags: ' + config.record.tagNames + '

Will be merged as

', 109 | border: false, 110 | bodyCssClass: 'panel-desc tagger-window-desc', 111 | readOnly: true 112 | },{ 113 | xtype: 'textfield', 114 | fieldLabel: _('tagger.tag.name'), 115 | name: 'name', 116 | anchor: '100%', 117 | allowBlank: false 118 | },{ 119 | xtype: 'tagger-combo-group', 120 | fieldLabel: _('tagger.tag.group'), 121 | name: 'group', 122 | anchor: '100%', 123 | allowBlank: false, 124 | readOnly: true, 125 | disable: true, 126 | cls: 'x-item-disabled' 127 | }]; 128 | } 129 | }); 130 | Ext.reg('tagger-window-merge-tags',tagger.window.MergeTags); 131 | -------------------------------------------------------------------------------- /core/components/tagger/src/Events/OnDocFormSave.php: -------------------------------------------------------------------------------- 1 | modx->getOption('resource', $this->scriptProperties, ''); 15 | 16 | /** @var TaggerGroup[] $groups */ 17 | $groups = $this->modx->getIterator(TaggerGroup::class); 18 | 19 | foreach ($groups as $group) { 20 | $showForTemplates = $group->show_for_templates; 21 | $showForTemplates = \Tagger\Utils::explodeAndClean($showForTemplates); 22 | $showForTemplates = array_flip($showForTemplates); 23 | 24 | $showForContexts = $group->show_for_contexts; 25 | $showForContexts = \Tagger\Utils::explodeAndClean($showForContexts); 26 | $showForContexts = array_flip($showForContexts); 27 | 28 | if (!isset($showForTemplates[$resource->template])) { 29 | continue; 30 | } 31 | 32 | if (!empty($showForContexts) && !isset($showForContexts[$resource->context_key])) { 33 | continue; 34 | } 35 | 36 | $oldTagsQuery = $this->modx->newQuery(TaggerTagResource::class); 37 | $oldTagsQuery->leftJoin(TaggerTag::class, 'Tag'); 38 | $oldTagsQuery->where(['resource' => $resource->id, 'Tag.group' => $group->id]); 39 | $oldTagsQuery->select( 40 | $this->modx->getSelectColumns(TaggerTagResource::class, 'TaggerTagResource', '', ['tag']) 41 | ); 42 | 43 | $oldTagsQuery->prepare(); 44 | $oldTagsQuery->stmt->execute(); 45 | $oldTags = $oldTagsQuery->stmt->fetchAll(\PDO::FETCH_COLUMN, 0); 46 | $oldTags = array_flip($oldTags); 47 | 48 | $tags = $resource->get('tagger-' . $group->id); 49 | if ($tags === null) { 50 | continue; 51 | } 52 | 53 | if (isset($tags)) { 54 | $tags = \Tagger\Utils::explodeAndClean($tags); 55 | 56 | foreach ($tags as $tag) { 57 | /** @var TaggerTag $tagObject */ 58 | $tagObject = $this->modx->getObject(TaggerTag::class, ['tag' => $tag, 'group' => $group->id]); 59 | if ($tagObject) { 60 | $existsRelation = $this->modx->getObject( 61 | TaggerTagResource::class, 62 | ['tag' => $tagObject->id, 'resource' => $resource->id] 63 | ); 64 | if ($existsRelation) { 65 | if (isset($oldTags[$existsRelation->tag])) { 66 | unset($oldTags[$existsRelation->tag]); 67 | } 68 | 69 | continue; 70 | } 71 | } 72 | 73 | if (!$tagObject) { 74 | if (!$group->allow_new) { 75 | continue; 76 | } 77 | 78 | $tagObject = $this->modx->newObject(TaggerTag::class); 79 | $tagObject->set('tag', $tag); 80 | $tagObject->addOne($group, 'Group'); 81 | $tagObject->save(); 82 | } 83 | 84 | /** @var TaggerTagResource $relationObject */ 85 | $relationObject = $this->modx->newObject(TaggerTagResource::class); 86 | $relationObject->set('tag', $tagObject->id); 87 | $relationObject->set('resource', $resource->id); 88 | $relationObject->save(); 89 | } 90 | } 91 | 92 | if (count($oldTags) > 0) { 93 | $oldTags = array_keys($oldTags); 94 | $this->modx->removeCollection( 95 | TaggerTagResource::class, 96 | [ 97 | 'tag:IN' => $oldTags, 98 | 'AND:resource:=' => $resource->id, 99 | ] 100 | ); 101 | } 102 | 103 | if ($group->remove_unused) { 104 | $c = $this->modx->newQuery(TaggerTagResource::class); 105 | $c->select($this->modx->getSelectColumns(TaggerTagResource::class, 'TaggerTagResource', '', ['tag'])); 106 | $c->prepare(); 107 | $c->stmt->execute(); 108 | $IDs = $c->stmt->fetchAll(\PDO::FETCH_COLUMN, 0); 109 | 110 | $IDs = array_keys(array_flip($IDs)); 111 | 112 | if (count($IDs) > 0) { 113 | $this->modx->removeCollection(TaggerTag::class, ['id:NOT IN' => $IDs, 'group' => $group->id]); 114 | } 115 | } 116 | } 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /core/components/tagger/elements/snippets/TaggerGetCurrentTag.php: -------------------------------------------------------------------------------- 1 | services->get('tagger'); 36 | 37 | $target = (int)$modx->getOption('target', $scriptProperties, $modx->resource->id, true); 38 | $tagTpl = $modx->getOption('tagTpl', $scriptProperties, ''); 39 | $groupTpl = $modx->getOption('groupTpl', $scriptProperties, ''); 40 | $outTpl = $modx->getOption('outTpl', $scriptProperties, ''); 41 | $tagSeparator = $modx->getOption('tagSeparator', $scriptProperties, ''); 42 | $groupSeparator = $modx->getOption('groupSeparator', $scriptProperties, ''); 43 | 44 | $friendlyURL = (int)$modx->getOption('friendlyURL', $scriptProperties, $modx->getOption('friendly_urls', null, 0)); 45 | $linkTagScheme = $modx->getOption('linkTagScheme', $scriptProperties, $modx->getOption('link_tag_scheme', null, -1)); 46 | 47 | $currentTags = $tagger->getCurrentTags(); 48 | $currentTagsLink = []; 49 | 50 | foreach ($currentTags as $currentTag) { 51 | $currentTagsLink[$currentTag['alias']] = array_keys($currentTag['tags']); 52 | } 53 | 54 | $output = []; 55 | 56 | foreach ($currentTags as $currentTag) { 57 | if (!isset($currentTag['tags'])) { 58 | continue; 59 | } 60 | 61 | $tags = []; 62 | 63 | foreach ($currentTag['tags'] as $tag) { 64 | $linkData = $currentTags; 65 | unset($linkData[$currentTag['alias']]['tags'][$tag['alias']]); 66 | if (count($linkData[$currentTag['alias']]['tags']) === 0) { 67 | unset($linkData[$currentTag['alias']]); 68 | } 69 | 70 | if ($friendlyURL === 1) { 71 | $linkPath = array_reduce( 72 | array_keys($linkData), 73 | function ($carry, $item) use ($linkData) { 74 | return $carry . $item . '/' . implode('/', array_unique(array_keys($linkData[$item]['tags']))) 75 | . '/'; 76 | }, 77 | '' 78 | ); 79 | 80 | $uri = rtrim($modx->makeUrl($target, '', '', $linkTagScheme), '/') . '/' . $linkPath; 81 | } else { 82 | $args = []; 83 | foreach ($linkData as $group) { 84 | $args[$group['alias']] = implode(',', array_keys($group['tags'])); 85 | } 86 | 87 | $uri = $modx->makeUrl($target, '', $args, $linkTagScheme); 88 | } 89 | 90 | $phs = [ 91 | 'tag' => $tag['tag'], 92 | 'label' => $tag['label'], 93 | 'alias' => $tag['alias'], 94 | 'uri' => $uri, 95 | 'group_name' => $currentTag['group'], 96 | 'group_alias' => $currentTag['alias'], 97 | ]; 98 | 99 | if (empty($tagTpl)) { 100 | $tags[] = '
' . print_r($phs, true) . '
'; 101 | } else { 102 | $tags[] = $tagger->getChunk($tagTpl, $phs); 103 | } 104 | } 105 | 106 | $groupPhs = [ 107 | 'name' => $currentTag['group'], 108 | 'alias' => $currentTag['alias'], 109 | 'multipleTags' => intval(count($tags) > 1), 110 | 'tags' => implode($tagSeparator, $tags), 111 | ]; 112 | 113 | if (empty($groupTpl)) { 114 | $output[] = '
' . print_r($groupPhs, true) . '
'; 115 | } else { 116 | $output[] = $tagger->getChunk($groupTpl, $groupPhs); 117 | } 118 | } 119 | 120 | if (!empty($outTpl) && !empty($output)) { 121 | return $tagger->getChunk( 122 | $outTpl, 123 | [ 124 | 'groups' => implode($groupSeparator, $output), 125 | ] 126 | ); 127 | } 128 | 129 | return implode($groupSeparator, $output); -------------------------------------------------------------------------------- /core/components/tagger/src/Processors/Group/Import.php: -------------------------------------------------------------------------------- 1 | setSupportedTypes(); 35 | 36 | $requiredOK = $this->checkRequired(); 37 | if ($requiredOK !== true) { 38 | return $this->failure($requiredOK); 39 | } 40 | 41 | $this->loadTags(); 42 | $this->processTags(); 43 | $this->saveTags(); 44 | $this->assignTagsToResources(); 45 | 46 | 47 | return $this->success(); 48 | } 49 | 50 | public function setSupportedTypes() 51 | { 52 | $this->supportedTypes = [ 53 | 'listbox-multiple' => '||', 54 | 'autotag' => ',', 55 | ]; 56 | } 57 | 58 | public function checkRequired() 59 | { 60 | $this->tv = $this->getProperty('tv'); 61 | $this->group = $this->getProperty('group'); 62 | 63 | if (empty($this->tv)) { 64 | $this->addFieldError('tv', $this->modx->lexicon('tagger.err.import_tv_ns')); 65 | } 66 | 67 | if (empty($this->group)) { 68 | $this->addFieldError('group', $this->modx->lexicon('tagger.err.import_group_ns')); 69 | } 70 | 71 | /** @var modTemplateVar $tv */ 72 | $tv = $this->modx->getObject(modTemplateVar::class, $this->tv); 73 | if ($tv) { 74 | if (isset($this->supportedTypes[$tv->type])) { 75 | $this->separator = $this->supportedTypes[$tv->type]; 76 | } else { 77 | $this->addFieldError( 78 | 'tv', 79 | $this->modx->lexicon( 80 | 'tagger.err.import_tv_nsp', 81 | ['supported' => implode(', ', array_keys($this->supportedTypes))] 82 | ) 83 | ); 84 | } 85 | } else { 86 | $this->addFieldError('tv', $this->modx->lexicon('tagger.err.import_tv_ne')); 87 | } 88 | 89 | return !$this->hasErrors(); 90 | } 91 | 92 | public function loadTags() 93 | { 94 | $c = $this->modx->newQuery(modTemplateVarResource::class); 95 | $c->where( 96 | [ 97 | 'tmplvarid' => $this->tv, 98 | ] 99 | ); 100 | 101 | $c->prepare(); 102 | $c->stmt->execute(); 103 | 104 | while ($row = $c->stmt->fetch(\PDO::FETCH_ASSOC)) { 105 | $this->tags[] = $row['modTemplateVarResource_value']; 106 | $this->resources[$row['modTemplateVarResource_contentid']] = \Tagger\Utils::explodeAndClean( 107 | $row['modTemplateVarResource_value'], 108 | $this->separator 109 | ); 110 | } 111 | } 112 | 113 | public function processTags() 114 | { 115 | $this->tags = implode($this->separator, $this->tags); 116 | $this->tags = \Tagger\Utils::explodeAndClean($this->tags, $this->separator); 117 | } 118 | 119 | public function saveTags() 120 | { 121 | $tags = $this->tags; 122 | $this->tags = []; 123 | 124 | foreach ($tags as $tag) { 125 | /** @var TaggerTag $tagObject */ 126 | $tagObject = $this->modx->getObject( 127 | TaggerTag::class, 128 | [ 129 | 'tag' => $tag, 130 | 'group' => $this->group, 131 | ] 132 | ); 133 | 134 | if (!$tagObject) { 135 | $tagObject = $this->modx->newObject(TaggerTag::class); 136 | $tagObject->set('tag', $tag); 137 | $tagObject->set('group', $this->group); 138 | $tagObject->save(); 139 | } 140 | 141 | $this->tags[$tag] = $tagObject->id; 142 | } 143 | } 144 | 145 | public function assignTagsToResources() 146 | { 147 | foreach ($this->resources as $resource => $tags) { 148 | foreach ($tags as $tag) { 149 | $relationExists = $this->modx->getObject( 150 | TaggerTagResource::class, 151 | ['tag' => (int)$this->tags[$tag], 'resource' => $resource] 152 | ); 153 | if ($relationExists) { 154 | continue; 155 | } 156 | 157 | $tagResource = $this->modx->newObject(TaggerTagResource::class); 158 | $tagResource->set('tag', (int)$this->tags[$tag]); 159 | $tagResource->set('resource', $resource); 160 | $tagResource->save(); 161 | } 162 | } 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /core/components/tagger/schema/tagger.mysql.schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /core/components/tagger/src/Model/mysql/TaggerTag.php: -------------------------------------------------------------------------------- 1 | 'Tagger\\Model\\', 11 | 'version' => '3.0', 12 | 'table' => 'tagger_tags', 13 | 'tableMeta' => 14 | array ( 15 | 'engine' => 'InnoDB', 16 | ), 17 | 'fields' => 18 | array ( 19 | 'tag' => NULL, 20 | 'label' => '', 21 | 'alias' => NULL, 22 | 'group' => NULL, 23 | 'rank' => 0, 24 | ), 25 | 'fieldMeta' => 26 | array ( 27 | 'tag' => 28 | array ( 29 | 'dbtype' => 'varchar', 30 | 'precision' => '100', 31 | 'phptype' => 'string', 32 | 'null' => false, 33 | ), 34 | 'label' => 35 | array ( 36 | 'dbtype' => 'varchar', 37 | 'precision' => '100', 38 | 'phptype' => 'string', 39 | 'null' => false, 40 | 'default' => '', 41 | ), 42 | 'alias' => 43 | array ( 44 | 'dbtype' => 'varchar', 45 | 'precision' => '100', 46 | 'phptype' => 'string', 47 | 'null' => false, 48 | ), 49 | 'group' => 50 | array ( 51 | 'dbtype' => 'integer', 52 | 'attributes' => 'unsigned', 53 | 'precision' => '10', 54 | 'phptype' => 'int', 55 | 'null' => false, 56 | ), 57 | 'rank' => 58 | array ( 59 | 'dbtype' => 'integer', 60 | 'attributes' => 'unsigned', 61 | 'precision' => '10', 62 | 'phptype' => 'int', 63 | 'null' => false, 64 | 'default' => 0, 65 | ), 66 | ), 67 | 'indexes' => 68 | array ( 69 | 'iTagGroup' => 70 | array ( 71 | 'alias' => 'iTagGroup', 72 | 'primary' => false, 73 | 'unique' => true, 74 | 'type' => 'BTREE', 75 | 'columns' => 76 | array ( 77 | 'tag' => 78 | array ( 79 | 'length' => '', 80 | 'collation' => 'A', 81 | 'null' => false, 82 | ), 83 | 'group' => 84 | array ( 85 | 'length' => '', 86 | 'collation' => 'A', 87 | 'null' => false, 88 | ), 89 | ), 90 | ), 91 | 'iTag' => 92 | array ( 93 | 'alias' => 'iTag', 94 | 'primary' => false, 95 | 'unique' => false, 96 | 'type' => 'BTREE', 97 | 'columns' => 98 | array ( 99 | 'tag' => 100 | array ( 101 | 'length' => '', 102 | 'collation' => 'A', 103 | 'null' => false, 104 | ), 105 | ), 106 | ), 107 | 'iAlias' => 108 | array ( 109 | 'alias' => 'iAlias', 110 | 'primary' => false, 111 | 'unique' => false, 112 | 'type' => 'BTREE', 113 | 'columns' => 114 | array ( 115 | 'alias' => 116 | array ( 117 | 'length' => '', 118 | 'collation' => 'A', 119 | 'null' => false, 120 | ), 121 | ), 122 | ), 123 | 'iGroup' => 124 | array ( 125 | 'alias' => 'iGroup', 126 | 'primary' => false, 127 | 'unique' => false, 128 | 'type' => 'BTREE', 129 | 'columns' => 130 | array ( 131 | 'group' => 132 | array ( 133 | 'length' => '', 134 | 'collation' => 'A', 135 | 'null' => false, 136 | ), 137 | ), 138 | ), 139 | ), 140 | 'composites' => 141 | array ( 142 | 'Resources' => 143 | array ( 144 | 'class' => 'Tagger\\Model\\TaggerTagResource', 145 | 'local' => 'id', 146 | 'foreign' => 'tag', 147 | 'cardinality' => 'many', 148 | 'owner' => 'local', 149 | ), 150 | ), 151 | 'aggregates' => 152 | array ( 153 | 'Group' => 154 | array ( 155 | 'class' => 'Tagger\\Model\\TaggerGroup', 156 | 'local' => 'group', 157 | 'foreign' => 'id', 158 | 'cardinality' => 'one', 159 | 'owner' => 'foreign', 160 | ), 161 | ), 162 | ); 163 | 164 | } 165 | -------------------------------------------------------------------------------- /core/components/tagger/elements/snippets/TaggerGetRelatedWhere.php: -------------------------------------------------------------------------------- 1 | services->get('tagger'); 55 | 56 | $resources = $modx->getOption( 57 | 'resources', 58 | $scriptProperties, 59 | is_object($modx->resource) ? $modx->resource->get('id') : '' 60 | ); 61 | $groups = $modx->getOption('groups', $scriptProperties, ''); 62 | $showUnused = (int)$modx->getOption('showUnused', $scriptProperties, '0'); 63 | $showUnpublished = (int)$modx->getOption('showUnpublished', $scriptProperties, '0'); 64 | $showDeleted = (int)$modx->getOption('showDeleted', $scriptProperties, '0'); 65 | $contexts = $modx->getOption('contexts', $scriptProperties, ''); 66 | 67 | $totalPh = $modx->getOption('totalPh', $scriptProperties, 'tags_total'); 68 | 69 | $resources = \Tagger\Utils::explodeAndClean($resources); 70 | $groups = \Tagger\Utils::explodeAndClean($groups); 71 | $contexts = \Tagger\Utils::explodeAndClean($contexts); 72 | 73 | $c = $modx->newQuery(TaggerTag::class); 74 | 75 | $c->leftJoin(TaggerTagResource::class, 'Resources'); 76 | $c->leftJoin(TaggerGroup::class, 'Group'); 77 | $c->leftJoin(modResource::class, 'Resource', ['Resources.resource = Resource.id']); 78 | 79 | 80 | if (!empty($contexts)) { 81 | $c->where(['Resource.context_key:IN' => $contexts,]); 82 | } 83 | 84 | if ($showUnpublished == 0) { 85 | $c->where( 86 | [ 87 | 'Resource.published' => 1, 88 | 'OR:Resource.published:IN' => null, 89 | ] 90 | ); 91 | } 92 | 93 | if ($showDeleted == 0) { 94 | $c->where( 95 | [ 96 | 'Resource.deleted' => 0, 97 | 'OR:Resource.deleted:IS' => null, 98 | ] 99 | ); 100 | } 101 | 102 | if ($showUnused == 0) { 103 | $c->having(['cnt > 0',]); 104 | } 105 | 106 | if ($resources) { 107 | $c->where(['Resources.resource:IN' => $resources]); 108 | } 109 | 110 | if ($groups) { 111 | $c->where( 112 | [ 113 | 'Group.id:IN' => $groups, 114 | 'OR:Group.name:IN' => $groups, 115 | 'OR:Group.alias:IN' => $groups, 116 | ] 117 | ); 118 | } 119 | $c->select($modx->getSelectColumns(TaggerTag::class, 'TaggerTag')); 120 | //$c->select($modx->getSelectColumns('TaggerGroup', 'Group', 'group_')); 121 | $c->select(['cnt' => 'COUNT(Resources.tag)']); 122 | $c->groupby( 123 | $modx->getSelectColumns(TaggerTag::class, 'TaggerTag') . ',' . $modx->getSelectColumns(TaggerGroup::class, 'Group') 124 | ); 125 | 126 | $c->prepare(); 127 | $countQuery = new xPDOCriteria($modx, "SELECT COUNT(*) FROM ({$c->toSQL(false)}) cq", $c->bindings, $c->cacheFlag); 128 | $stmt = $countQuery->prepare(); 129 | if ($stmt && $stmt->execute()) { 130 | $total = intval($stmt->fetchColumn()); 131 | } else { 132 | $total = 0; 133 | } 134 | 135 | $modx->setPlaceholder($totalPh, $total); 136 | 137 | $tags = []; 138 | 139 | if ($collection = $modx->getIterator('TaggerTag', $c)) { 140 | foreach ($collection as $tag) { 141 | $tags[] = $tag->get('alias'); 142 | } 143 | } 144 | 145 | $wherecondition = ['id:not IN' => $resources]; 146 | 147 | $scriptProperties['where'] = $modx->toJson($wherecondition); 148 | 149 | $output = '{"template":"99999999"}'; 150 | 151 | if (count($tags)) { 152 | $scriptProperties['tags'] = implode(',', $tags); 153 | $output = $modx->runSnippet('TaggerGetResourcesWhere', $scriptProperties); 154 | } 155 | 156 | return $output; -------------------------------------------------------------------------------- /core/components/tagger/src/Tagger.php: -------------------------------------------------------------------------------- 1 | modx =& $modx; 29 | 30 | $corePath = $this->getOption( 31 | 'core_path', 32 | $options, 33 | $this->modx->getOption('core_path', null, MODX_CORE_PATH) . 'components/tagger/' 34 | ); 35 | $assetsPath = $this->getOption( 36 | 'assets_path', 37 | $options, 38 | $this->modx->getOption('assets_path', null, MODX_ASSETS_PATH) . 'components/tagger/' 39 | ); 40 | $assetsUrl = $this->getOption( 41 | 'assets_url', 42 | $options, 43 | $this->modx->getOption('assets_url', null, MODX_ASSETS_URL) . 'components/tagger/' 44 | ); 45 | 46 | /* loads some default paths for easier management */ 47 | $this->options = array_merge( 48 | [ 49 | 'namespace' => $this->namespace, 50 | 'corePath' => $corePath, 51 | 'modelPath' => $corePath . 'model/', 52 | 'chunksPath' => $corePath . 'elements/chunks/', 53 | 'snippetsPath' => $corePath . 'elements/snippets/', 54 | 'templatesPath' => $corePath . 'templates/', 55 | 'assetsPath' => $assetsPath, 56 | 'assetsUrl' => $assetsUrl, 57 | 'jsUrl' => $assetsUrl . 'js/', 58 | 'cssUrl' => $assetsUrl . 'css/', 59 | ], 60 | $options 61 | ); 62 | 63 | $this->modx->lexicon->load('tagger:default'); 64 | $this->modx->lexicon->load('tagger:custom'); 65 | } 66 | 67 | /** 68 | * Get a local configuration option or a namespaced system setting by key. 69 | * 70 | * @param string $key The option key to search for. 71 | * @param array $options An array of options that override local options. 72 | * @param mixed $default The default value returned if the option is not found locally or as a 73 | * namespaced system setting; by default this value is null. 74 | * 75 | * @return mixed The option value or the default value specified. 76 | */ 77 | public function getOption($key, $options = [], $default = null) 78 | { 79 | $option = $default; 80 | if (!empty($key) && is_string($key)) { 81 | if ($options != null && array_key_exists($key, $options)) { 82 | $option = $options[$key]; 83 | } elseif (array_key_exists($key, $this->options)) { 84 | $option = $this->options[$key]; 85 | } elseif (array_key_exists("{$this->namespace}.{$key}", $this->modx->config)) { 86 | $option = $this->modx->getOption("{$this->namespace}.{$key}"); 87 | } 88 | } 89 | return $option; 90 | } 91 | 92 | public function getChunk($tpl, $phs = []) 93 | { 94 | if (strpos($tpl, '@INLINE ') !== false) { 95 | $content = str_replace('@INLINE', '', $tpl); 96 | /** @var modChunk $chunk */ 97 | $chunk = $this->modx->newObject(modChunk::class, ['name' => 'inline-' . uniqid()]); 98 | $chunk->setCacheable(false); 99 | 100 | return $chunk->process($phs, $content); 101 | } 102 | 103 | return $this->modx->getChunk($tpl, $phs); 104 | } 105 | 106 | public function getCurrentTags() 107 | { 108 | $currentTags = []; 109 | 110 | /** @var TaggerGroup[] $groups */ 111 | $groups = $this->modx->getIterator(TaggerGroup::class); 112 | foreach ($groups as $group) { 113 | if (isset($_GET[$group->alias])) { 114 | $groupTags = Utils::explodeAndClean($_GET[$group->alias]); 115 | if (!empty($groupTags)) { 116 | $tags = []; 117 | foreach ($groupTags as $groupTag) { 118 | /** @var TaggerTag $tag */ 119 | $tag = $this->modx->getObject( 120 | TaggerTag::class, 121 | [ 122 | ['id' => $groupTag, 'OR:alias:=' => $groupTag], 123 | 'group' => $group->id, 124 | ] 125 | ); 126 | if ($tag) { 127 | $tags[$tag->alias] = [ 128 | 'tag' => $tag->tag, 129 | 'label' => $tag->label, 130 | 'alias' => $tag->alias, 131 | ]; 132 | } else { 133 | $tags[$groupTag] = [ 134 | 'tag' => $groupTag, 135 | 'alias' => $groupTag, 136 | ]; 137 | } 138 | } 139 | 140 | $currentTags[$group->alias] = [ 141 | 'group' => $group->name, 142 | 'alias' => $group->alias, 143 | 'tags' => $tags, 144 | ]; 145 | } 146 | } 147 | } 148 | 149 | return $currentTags; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /_build/gpm.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tagger", 3 | "lowCaseName": "tagger", 4 | "description": "Tag management component", 5 | "author": "John Peca", 6 | "version": "2.1.0-pl", 7 | "menus": [ 8 | { 9 | "text": "tagger.menu.tagger", 10 | "description": "tagger.menu.tagger_desc", 11 | "action": "home" 12 | } 13 | ], 14 | "plugins": [ 15 | { 16 | "name": "Tagger", 17 | "events": [ 18 | "OnDocFormSave", 19 | "OnDocFormPrerender", 20 | "OnPageNotFound", 21 | "OnResourceDuplicate" 22 | ] 23 | } 24 | ], 25 | "snippets": [ 26 | { 27 | "name": "TaggerGetTags", 28 | "properties": [ 29 | { 30 | "name": "resources", 31 | "description": "tagger.gettags.resources_desc", 32 | "value": "" 33 | }, 34 | { 35 | "name": "groups", 36 | "description": "tagger.gettags.groups_desc", 37 | "value": "" 38 | }, 39 | { 40 | "name": "rowTpl", 41 | "description": "tagger.gettags.rowTpl_desc", 42 | "value": "" 43 | }, 44 | { 45 | "name": "outTpl", 46 | "description": "tagger.gettags.outTpl_desc", 47 | "value": "" 48 | }, 49 | { 50 | "name": "separator", 51 | "description": "tagger.gettags.separator_desc", 52 | "value": "" 53 | }, 54 | { 55 | "name": "target", 56 | "description": "tagger.gettags.target_desc", 57 | "value": "" 58 | }, 59 | { 60 | "name": "showUnused", 61 | "description": "tagger.gettags.showUnused_desc", 62 | "type": "numberfield", 63 | "value": "0" 64 | }, 65 | { 66 | "name": "showUnpublished", 67 | "description": "tagger.gettags.showUnpublished_desc", 68 | "type": "numberfield", 69 | "value": "0" 70 | }, 71 | { 72 | "name": "showDeleted", 73 | "description": "tagger.gettags.showDeleted_desc", 74 | "type": "numberfield", 75 | "value": "0" 76 | }, 77 | { 78 | "name": "contexts", 79 | "description": "tagger.gettags.contexts_desc", 80 | "value": "" 81 | }, 82 | { 83 | "name": "toPlaceholder", 84 | "description": "tagger.gettags.toPlaceholder_desc", 85 | "value": "" 86 | }, 87 | { 88 | "name": "limit", 89 | "description": "tagger.gettags.limit_desc", 90 | "type": "numberfield", 91 | "value": "0" 92 | }, 93 | { 94 | "name": "offset", 95 | "description": "tagger.gettags.offset_desc", 96 | "type": "numberfield", 97 | "value": "0" 98 | }, 99 | { 100 | "name": "totalPh", 101 | "description": "tagger.gettags.totalPh_desc", 102 | "value": "tags_total" 103 | }, 104 | { 105 | "name": "sort", 106 | "description": "tagger.gettags.sort_desc", 107 | "value": "{\"tag\": \"asc\"}" 108 | } 109 | ] 110 | }, 111 | { 112 | "name": "TaggerGetResourcesWhere", 113 | "properties": [ 114 | { 115 | "name": "tags", 116 | "description": "tagger.getresourceswhere.tags_desc", 117 | "value": "" 118 | }, 119 | { 120 | "name": "groups", 121 | "description": "tagger.getresourceswhere.groups_desc", 122 | "value": "" 123 | }, 124 | { 125 | "name": "where", 126 | "description": "tagger.getresourceswhere.where_desc", 127 | "value": "" 128 | }, 129 | { 130 | "name": "likeComparison", 131 | "description": "tagger.getresourceswhere.likeComparison_desc", 132 | "type": "numberfield", 133 | "value": "0" 134 | }, 135 | { 136 | "name": "tagField", 137 | "description": "tagger.getresourceswhere.tagField_desc", 138 | "value": "alias" 139 | }, 140 | { 141 | "name": "matchAll", 142 | "description": "tagger.getresourceswhere.matchAll_desc", 143 | "type": "numberfield", 144 | "value": "0" 145 | } 146 | ] 147 | }, 148 | { 149 | "name": "TaggerGetRelatedWhere" 150 | }, 151 | { 152 | "name": "TaggerGetCurrentTag" 153 | } 154 | ], 155 | "systemSettings": [ 156 | { 157 | "key": "place_above_content_header", 158 | "value": 1, 159 | "type": "combo-boolean", 160 | "area": "places" 161 | }, 162 | { 163 | "key": "place_below_content_header", 164 | "value": 1, 165 | "type": "combo-boolean", 166 | "area": "places" 167 | }, 168 | { 169 | "key": "place_bottom_page_header", 170 | "value": 1, 171 | "type": "combo-boolean", 172 | "area": "places" 173 | }, 174 | { 175 | "key": "place_in_tab_label", 176 | "value": "tagger.tab.label", 177 | "area": "places" 178 | }, 179 | { 180 | "key": "place_tvs_tab_label", 181 | "value": "tagger.tab.label", 182 | "area": "places" 183 | }, 184 | { 185 | "key": "place_above_content_label", 186 | "value": "tagger.tab.label", 187 | "area": "places" 188 | }, 189 | { 190 | "key": "place_below_content_label", 191 | "value": "tagger.tab.label", 192 | "area": "places" 193 | }, 194 | { 195 | "key": "place_bottom_page_label", 196 | "value": "tagger.tab.label", 197 | "area": "places" 198 | }, 199 | { 200 | "key": "remove_accents_tag", 201 | "value": 1, 202 | "type": "combo-boolean", 203 | "area": "settings" 204 | }, 205 | { 206 | "key": "remove_accents_group", 207 | "value": 1, 208 | "type": "combo-boolean", 209 | "area": "settings" 210 | } 211 | ], 212 | "database": { 213 | "tables": [ 214 | "\\Tagger\\Model\\TaggerGroup", 215 | "\\Tagger\\Model\\TaggerTag", 216 | "\\Tagger\\Model\\TaggerTagResource" 217 | ] 218 | }, 219 | "build": { 220 | "scriptsAfter": [ 221 | "after.migration.php" 222 | ], 223 | "requires": { 224 | "gpm": ">=3.0.0", 225 | "modx": ">=3.0.0-alpha" 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /core/components/tagger/elements/snippets/TaggerGetResourcesWhere.php: -------------------------------------------------------------------------------- 1 | getOption('tags', $scriptProperties, ''); 36 | $where = $modx->getOption('where', $scriptProperties, ''); 37 | $tagField = $modx->getOption('tagField', $scriptProperties, 'alias'); 38 | $likeComparison = (int)$modx->getOption('likeComparison', $scriptProperties, 0); 39 | $matchAll = (int)$modx->getOption('matchAll', $scriptProperties, 0); 40 | $errorOnInvalidTags = (int)$modx->getOption('errorOnInvalidTags', $scriptProperties, 0); 41 | $field = $modx->getOption('field', $scriptProperties, 'id'); 42 | $where = $modx->fromJSON($where); 43 | if ($where == false) { 44 | $where = []; 45 | } 46 | 47 | $tagsCount = 0; 48 | 49 | if ($tags == '') { 50 | $gc = $modx->newQuery(TaggerGroup::class); 51 | $gc->select($modx->getSelectColumns(TaggerGroup::class, '', '', ['alias'])); 52 | 53 | $groups = $modx->getOption('groups', $scriptProperties, ''); 54 | $groups = \Tagger\Utils::explodeAndClean($groups); 55 | if (!empty($groups)) { 56 | $gc->where( 57 | [ 58 | 'name:IN' => $groups, 59 | 'OR:alias:IN' => $groups, 60 | 'OR:id:IN' => $groups, 61 | ] 62 | ); 63 | } 64 | 65 | $gc->prepare(); 66 | $gc->stmt->execute(); 67 | $groups = $gc->stmt->fetchAll(PDO::FETCH_COLUMN, 0); 68 | 69 | $conditions = []; 70 | foreach ($groups as $group) { 71 | if (isset($_GET[$group])) { 72 | $groupTags = \Tagger\Utils::explodeAndClean($_GET[$group]); 73 | if (!empty($groupTags)) { 74 | $like = ['AND:alias:IN' => $groupTags]; 75 | 76 | if ($likeComparison == 1) { 77 | foreach ($groupTags as $tag) { 78 | $like[] = ['OR:alias:LIKE' => '%' . $tag . '%']; 79 | } 80 | } 81 | 82 | $conditions[] = [ 83 | 'OR:Group.alias:=' => $group, 84 | $like, 85 | ]; 86 | $tagsCount += count($groupTags); 87 | } 88 | } 89 | } 90 | 91 | if (count($conditions) == 0) { 92 | return $modx->toJSON($where); 93 | } 94 | 95 | $c = $modx->newQuery(TaggerTag::class); 96 | $c->leftJoin(TaggerGroup::class, 'Group'); 97 | $c->select($modx->getSelectColumns(TaggerTag::class, 'TaggerTag', '', ['id', 'group'])); 98 | 99 | $c->where($conditions); 100 | } else { 101 | $tags = \Tagger\Utils::explodeAndClean($tags); 102 | 103 | if (empty($tags)) { 104 | return $modx->toJSON($where); 105 | } 106 | 107 | $tagsCount = count($tags); 108 | 109 | $groups = $modx->getOption('groups', $scriptProperties, ''); 110 | 111 | $groups = \Tagger\Utils::explodeAndClean($groups); 112 | 113 | $c = $modx->newQuery(TaggerTag::class); 114 | $c->select($modx->getSelectColumns(TaggerTag::class, 'TaggerTag', '', ['id', 'group'])); 115 | 116 | $compare = [ 117 | $tagField . ':IN' => $tags, 118 | ]; 119 | 120 | if ($likeComparison == 1) { 121 | foreach ($tags as $tag) { 122 | $compare[] = ['OR:' . $tagField . ':LIKE' => '%' . $tag . '%']; 123 | } 124 | } 125 | 126 | $c->where($compare); 127 | 128 | if (!empty($groups)) { 129 | $c->leftJoin(TaggerGroup::class, 'Group'); 130 | $c->where( 131 | [ 132 | 'Group.id:IN' => $groups, 133 | 'OR:Group.name:IN' => $groups, 134 | 'OR:Group.alias:IN' => $groups, 135 | ] 136 | ); 137 | } 138 | } 139 | 140 | $c->prepare(); 141 | $c->stmt->execute(); 142 | 143 | $tagGroups = []; 144 | $tagIDs = []; 145 | if ($matchAll === 2) { 146 | // Group the tags 147 | $taggerTags = $c->stmt->fetchAll(PDO::FETCH_ASSOC); 148 | foreach ($taggerTags as $tag) { 149 | if (!array_key_exists($tag['group'], $tagGroups)) { 150 | $tagGroups[$tag['group']] = []; 151 | } 152 | $tagGroups[$tag['group']][] = $tag['id']; 153 | $tagIDs[] = $tag['id']; 154 | } 155 | } else { 156 | $tagIDs = $c->stmt->fetchAll(PDO::FETCH_COLUMN, 0); 157 | } 158 | 159 | if (count($tagIDs) == 0) { 160 | $tagIDs[] = 0; 161 | if ($errorOnInvalidTags == 1) { 162 | $modx->sendForward($modx->getOption('error_page'), ['response_code' => 'HTTP/1.1 404 Not Found']); 163 | } 164 | } 165 | 166 | if ($matchAll == 0) { 167 | $where[] = "EXISTS (SELECT 1 FROM {$modx->getTableName(TaggerTagResource::class)} r WHERE r.tag IN (" . implode( 168 | ',', 169 | $tagIDs 170 | ) . ") AND r.resource = modResource." . $field . ")"; 171 | } elseif ($matchAll === 2) { 172 | foreach ($tagGroups as $tagGroupIDs) { 173 | $where[] = "EXISTS (SELECT 1 FROM {$modx->getTableName(TaggerTagResource::class)} r WHERE r.tag IN (" . implode( 174 | ',', 175 | $tagGroupIDs 176 | ) . ") AND r.resource = modResource." . $field . ")"; 177 | } 178 | } else { 179 | $where[] = "EXISTS (SELECT 1 as found FROM {$modx->getTableName(TaggerTagResource::class)} r WHERE r.tag IN (" 180 | . implode(',', $tagIDs) . ") AND r.resource = modResource." . $field . " GROUP BY found HAVING count(found) = " 181 | . $tagsCount . ")"; 182 | } 183 | 184 | return $modx->toJSON($where); -------------------------------------------------------------------------------- /core/components/tagger/src/Model/mysql/TaggerGroup.php: -------------------------------------------------------------------------------- 1 | 'Tagger\\Model\\', 11 | 'version' => '3.0', 12 | 'table' => 'tagger_groups', 13 | 'tableMeta' => 14 | array ( 15 | 'engine' => 'InnoDB', 16 | ), 17 | 'fields' => 18 | array ( 19 | 'name' => NULL, 20 | 'alias' => NULL, 21 | 'field_type' => NULL, 22 | 'allow_new' => 0, 23 | 'remove_unused' => 0, 24 | 'allow_blank' => 0, 25 | 'allow_type' => 0, 26 | 'show_autotag' => 0, 27 | 'hide_input' => 0, 28 | 'tag_limit' => 0, 29 | 'show_for_templates' => '', 30 | 'place' => 'in-tab', 31 | 'position' => 0, 32 | 'description' => '', 33 | 'in_tvs_position' => 9999, 34 | 'as_radio' => 0, 35 | 'sort_field' => 'alias', 36 | 'sort_dir' => 'asc', 37 | 'show_for_contexts' => '', 38 | ), 39 | 'fieldMeta' => 40 | array ( 41 | 'name' => 42 | array ( 43 | 'dbtype' => 'varchar', 44 | 'precision' => '100', 45 | 'phptype' => 'string', 46 | 'null' => false, 47 | ), 48 | 'alias' => 49 | array ( 50 | 'dbtype' => 'varchar', 51 | 'precision' => '100', 52 | 'phptype' => 'string', 53 | 'null' => false, 54 | ), 55 | 'field_type' => 56 | array ( 57 | 'dbtype' => 'varchar', 58 | 'precision' => '100', 59 | 'phptype' => 'string', 60 | 'null' => false, 61 | ), 62 | 'allow_new' => 63 | array ( 64 | 'dbtype' => 'tinyint', 65 | 'attributes' => 'unsigned', 66 | 'precision' => '1', 67 | 'phptype' => 'boolean', 68 | 'null' => false, 69 | 'default' => 0, 70 | ), 71 | 'remove_unused' => 72 | array ( 73 | 'dbtype' => 'tinyint', 74 | 'attributes' => 'unsigned', 75 | 'precision' => '1', 76 | 'phptype' => 'boolean', 77 | 'null' => false, 78 | 'default' => 0, 79 | ), 80 | 'allow_blank' => 81 | array ( 82 | 'dbtype' => 'tinyint', 83 | 'attributes' => 'unsigned', 84 | 'precision' => '1', 85 | 'phptype' => 'boolean', 86 | 'null' => false, 87 | 'default' => 0, 88 | ), 89 | 'allow_type' => 90 | array ( 91 | 'dbtype' => 'tinyint', 92 | 'attributes' => 'unsigned', 93 | 'precision' => '1', 94 | 'phptype' => 'boolean', 95 | 'null' => false, 96 | 'default' => 0, 97 | ), 98 | 'show_autotag' => 99 | array ( 100 | 'dbtype' => 'tinyint', 101 | 'attributes' => 'unsigned', 102 | 'precision' => '1', 103 | 'phptype' => 'boolean', 104 | 'null' => false, 105 | 'default' => 0, 106 | ), 107 | 'hide_input' => 108 | array ( 109 | 'dbtype' => 'tinyint', 110 | 'attributes' => 'unsigned', 111 | 'precision' => '1', 112 | 'phptype' => 'boolean', 113 | 'null' => false, 114 | 'default' => 0, 115 | ), 116 | 'tag_limit' => 117 | array ( 118 | 'dbtype' => 'int', 119 | 'attributes' => 'unsigned', 120 | 'precision' => '10', 121 | 'phptype' => 'integer', 122 | 'null' => false, 123 | 'default' => 0, 124 | ), 125 | 'show_for_templates' => 126 | array ( 127 | 'dbtype' => 'text', 128 | 'phptype' => 'string', 129 | 'null' => false, 130 | 'default' => '', 131 | ), 132 | 'place' => 133 | array ( 134 | 'dbtype' => 'varchar', 135 | 'precision' => '100', 136 | 'phptype' => 'string', 137 | 'null' => false, 138 | 'default' => 'in-tab', 139 | ), 140 | 'position' => 141 | array ( 142 | 'dbtype' => 'int', 143 | 'attributes' => 'unsigned', 144 | 'precision' => '10', 145 | 'phptype' => 'integer', 146 | 'null' => false, 147 | 'default' => 0, 148 | ), 149 | 'description' => 150 | array ( 151 | 'dbtype' => 'text', 152 | 'phptype' => 'string', 153 | 'null' => false, 154 | 'default' => '', 155 | ), 156 | 'in_tvs_position' => 157 | array ( 158 | 'dbtype' => 'int', 159 | 'attributes' => 'unsigned', 160 | 'precision' => '10', 161 | 'phptype' => 'integer', 162 | 'null' => false, 163 | 'default' => 9999, 164 | ), 165 | 'as_radio' => 166 | array ( 167 | 'dbtype' => 'tinyint', 168 | 'attributes' => 'unsigned', 169 | 'precision' => '1', 170 | 'phptype' => 'boolean', 171 | 'null' => false, 172 | 'default' => 0, 173 | ), 174 | 'sort_field' => 175 | array ( 176 | 'dbtype' => 'varchar', 177 | 'precision' => '255', 178 | 'phptype' => 'string', 179 | 'null' => false, 180 | 'default' => 'alias', 181 | ), 182 | 'sort_dir' => 183 | array ( 184 | 'dbtype' => 'varchar', 185 | 'precision' => '255', 186 | 'phptype' => 'string', 187 | 'null' => false, 188 | 'default' => 'asc', 189 | ), 190 | 'show_for_contexts' => 191 | array ( 192 | 'dbtype' => 'text', 193 | 'phptype' => 'string', 194 | 'null' => false, 195 | 'default' => '', 196 | ), 197 | ), 198 | 'composites' => 199 | array ( 200 | 'Tags' => 201 | array ( 202 | 'class' => 'Tagger\\Model\\TaggerTag', 203 | 'local' => 'id', 204 | 'foreign' => 'group', 205 | 'cardinality' => 'many', 206 | 'owner' => 'local', 207 | ), 208 | ), 209 | ); 210 | 211 | } 212 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog for Tagger. 2 | 3 | Tagger 2.1.0 4 | ============== 5 | - Fix adding tags from multiple pages 6 | - Add &groupTpl to TaggerGetTags 7 | - Add &matchAll=2 to TaggerGetResourcesWhere 8 | 9 | Tagger 2.0.0 10 | ============== 11 | - Add support for Revolution 3.0.0 12 | - Remove group placement above/under content 13 | 14 | Tagger 1.12.0 15 | ============== 16 | - Add 404 handler for invalid tags. 17 | 18 | Tagger 1.11.0 19 | ============== 20 | - Move the Gateway handler from OnHandleRequest to OnPageNotFound 21 | - Fix FURL issue with Revo >= 2.7.0 22 | 23 | Tagger 1.10.0 24 | ============== 25 | - Add URI to TaggerGetCurrentTag snippet 26 | - Add linkOneTagPerGroup option to TaggerGetTags 27 | - Add option to include current tags in TaggerGetTags url 28 | - Add Tag Label for frontend usage 29 | - Add scheme override to TaggerGetTags properties 30 | - Add an option (through system settings) to preserve accent characters in tag/group alias 31 | 32 | Tagger 1.9.0 33 | ============== 34 | - Add noActiveTags placeholder to the outTpl of TaggerGetTags snippet 35 | - If TV tab is not present, add all Tagger groups to Tagger tab 36 | - Add ability to show Tagger only for specific contexts 37 | - Remove accent chars from tag's & group's alias 38 | - Fix adding tags from field 39 | - Add ability to sort tags in manager by rank or alias 40 | - Add sample translation to custom lexicon 41 | - Fix assign from field when tag limit is set 42 | - Fix alphabetic sorting of tags 43 | - Add As Radio option to the Tagger Group 44 | - Fix updating hidden textfield 45 | 46 | Tagger 1.8.0 47 | ============== 48 | - Sort tags by tag asc in the form 49 | - Sort tags by tag asc when suggesting them 50 | - Fix TaggerGetRelatedWhere to pass alias instead of tag 51 | - Add TaggerGetCurrentTag snippet 52 | - Add active placeholder to TaggerGetTags rowTpl to identify currently active tag 53 | - Add parent option to TaggerGetTags 54 | - RU lexicons (credits goes to mvoevodskiy) 55 | - Fix deleting multiple tags at once 56 | - Decode HTML entities when setting value 57 | - Include script properties to TaggerGetTags chunks 58 | 59 | Tagger 1.7.0 60 | ============== 61 | - Match group by alias or name in TaggerGetResourcesWhere 62 | - Add TaggerGetRelatedWhere snippet 63 | - Class for each plugin event 64 | - Hide tag limit field for combo box input 65 | - Fix gateway for index page 66 | - Load values for Tagger fields under TVs properly 67 | - Add field option to TaggerGetResourcesWhere 68 | - New styles for Revolution 2.3 69 | - Added @INLINE support in TaggerGetTags tpls params 70 | - Added &weight parameter to TaggerGetTags 71 | - Fixed matchAll parameter in TaggerGetResourcesWhere 72 | 73 | Tagger 1.6.0 74 | ============== 75 | - Added friendlyURL option to TaggerGetTags 76 | - Added matchAll option to TaggerGetResourcesWhere 77 | 78 | Tagger 1.5.1 79 | ============== 80 | - Fixed compatibility with PHP 5.2 81 | 82 | Tagger 1.5.0 83 | ============== 84 | - Added option to translate Tagger groups 85 | - Added system settings for changing place's labels 86 | - Added an option to place a group as a tab into TVs section 87 | - Added option &wrapIfEmpty 88 | 89 | Tagger 1.4.0 90 | ============== 91 | - Updated french translations 92 | - Fixed gateway to support all file extensions 93 | - Made combobox for tags 100% width 94 | - Put form labels to top 95 | - Added lexicon string for Tagger's tab 96 | - Updated styles to look better in 2.3 97 | - Added tagField to GetResourcesWhere snippet 98 | - Added sort param to GetTags snippet 99 | - Added Group description 100 | - Changed labelSeparator 101 | - Fixed notice in GetTags snippet 102 | - Fixed logging messages from plugin 103 | - Updated GetResourcesWhere snippet and removed tag_key init 104 | 105 | Tagger 1.3.1 106 | ============== 107 | - Fixed autotag styles in Revolution 2.3 108 | - Fixed tag's styles inside tag field in Revolution 2.3 109 | 110 | Tagger 1.3.0 111 | ============== 112 | - Fixed bug that removed all assigned tags on second resource's save 113 | - Added alias field to groups 114 | - Added alias field to tags 115 | - Added option to limit number of selected tags 116 | - Added option to use LIKE in query in TaggerGetResourcesWhere snippet 117 | - Fixed tag count when using showDeleted = 0 or showUnpublished = 0 118 | - Added an option to hide input field when auto tag is showed 119 | - Added limit, offset, totalPh, tpl_N properties to TaggerGetTags snippet 120 | - Fixed gateway redirecting to URL ended with a comma 121 | 122 | Tagger 1.2.0 123 | ============== 124 | - Added merge selected Tags 125 | - Added remove selected Tags 126 | - Fixed IndexManagerController 127 | - Added options showDeleted and showUnpublished to snippet getTags 128 | - Fixed getResourcesWhere to not return null when no tags are provided 129 | 130 | Tagger 1.1.0 131 | ============== 132 | - Added toPlaceholder property to getTags snippet 133 | - Duplicate Tags also for children 134 | - Improved gateway 135 | - Added handler for onResourceDuplicate event, that copies Tags (only for the Resource, not for children) 136 | - Added contexts property to TaggerGetTags snippet 137 | 138 | Tagger 1.0.2 139 | ============== 140 | - Replaced calling shorthand PHP array [] with default array() 141 | 142 | Tagger 1.0.1 143 | ============== 144 | - Fixed default value for &target in TaggerGetTags 145 | - Added descriptions to add/update group window 146 | - Fixed &group property in TaggerGetTags snippet 147 | 148 | Tagger 1.0.0 149 | ============== 150 | - Added ability to show assigned resources for selected tag from Tag grid 151 | - Added system settings to show/hide header of group places 152 | - Added option to select place where to render Tagger Group 153 | - Added import button to Tagger Group grid 154 | - Added drag & drop groups reorder 155 | - Added position to Tagger group 156 | 157 | Tagger 0.4.1 158 | ============== 159 | - Fixed group add dialog 160 | 161 | Tagger 0.4.0 162 | ============== 163 | - Added snippets properties 164 | - Translated system settings 165 | - Improved Snippets documentation 166 | - Added autotag option for groups that have field type set to Tag field 167 | - Fixed updating tags from manager 168 | - Added PHP documentation to xPDO classes 169 | - Added an option to show only used tags in TaggerGetTags 170 | - Added Allow type option to groups 171 | - Added trigger button to the tagger field 172 | 173 | Tagger 0.3.0 174 | ============== 175 | - Added url placeholder into TaggerGetTags row tpl 176 | - Added gateway for handling friendly URL with tags 177 | - Added snippet for creating WHERE query that can be used in getResources 178 | - Removed snippet for listing resources by tags 179 | 180 | Tagger 0.2.0 181 | ============== 182 | - Fixed removing tags after quick save 183 | - Fixed removing old tag - resource relations 184 | - Fixed removing unused tags 185 | - Added snippet for listing resources by tags 186 | - Added snippet for listing tags 187 | 188 | Tagger 0.1.0 189 | ============== 190 | - Updated schema - added indexes, removed ID from TaggerTagResource 191 | - Speed improvements in retrieving tags 192 | - Added groups options: field_type, allow_new, remove_unused and allow_blank 193 | - Added combobox for tags 194 | - Added tag field 195 | - Added sorting option to tag and group grids 196 | - Tag management 197 | - Group management 198 | - Initial release. 199 | -------------------------------------------------------------------------------- /assets/components/tagger/js/mgr/extras/tagger.combo.js: -------------------------------------------------------------------------------- 1 | tagger.combo.Group = function(config) { 2 | config = config || {}; 3 | Ext.applyIf(config,{ 4 | name: 'group', 5 | hiddenName: 'group', 6 | displayField: 'name', 7 | valueField: 'id', 8 | fields: ['name','id'], 9 | pageSize: 20, 10 | url: MODx.config.connector_url, 11 | baseParams:{ 12 | action: 'Tagger\\Processors\\Group\\GetList' 13 | } 14 | }); 15 | tagger.combo.Group.superclass.constructor.call(this,config); 16 | }; 17 | Ext.extend(tagger.combo.Group,MODx.combo.ComboBox); 18 | Ext.reg('tagger-combo-group',tagger.combo.Group); 19 | 20 | tagger.combo.Tag = function(config) { 21 | config = config || {}; 22 | Ext.applyIf(config,{ 23 | name: 'tag', 24 | hiddenName: 'tag', 25 | displayField: 'tag', 26 | valueField: 'id', 27 | fields: ['tag','id'], 28 | pageSize: 20, 29 | editable: config.allowAdd, 30 | forceSelection: !config.allowAdd, 31 | url: MODx.config.connector_url, 32 | baseParams: { 33 | action: 'Tagger\\Processors\\Tag\\GetList' 34 | } 35 | }); 36 | tagger.combo.Tag.superclass.constructor.call(this,config); 37 | }; 38 | Ext.extend(tagger.combo.Tag,MODx.combo.ComboBox); 39 | Ext.reg('tagger-combo-tag',tagger.combo.Tag); 40 | 41 | tagger.combo.FieldType = function(config) { 42 | config = config || {}; 43 | Ext.applyIf(config,{ 44 | store: new Ext.data.SimpleStore({ 45 | fields: ['d','v'], 46 | data: [ 47 | [_('tagger.field.tagfield') ,'tagger-field-tags'], 48 | [_('tagger.field.combobox') ,'tagger-combo-tag'] 49 | ] 50 | }), 51 | displayField: 'd', 52 | valueField: 'v', 53 | mode: 'local', 54 | value: 'tagger-field-tags', 55 | triggerAction: 'all', 56 | editable: false, 57 | selectOnFocus: false, 58 | preventRender: true, 59 | forceSelection: true, 60 | enableKeyEvents: true 61 | }); 62 | tagger.combo.FieldType.superclass.constructor.call(this,config); 63 | }; 64 | Ext.extend(tagger.combo.FieldType,MODx.combo.ComboBox); 65 | Ext.reg('tagger-combo-field-type',tagger.combo.FieldType); 66 | 67 | tagger.combo.Templates = function(config, getStore) { 68 | config = config || {}; 69 | Ext.applyIf(config,{ 70 | name: 'templates', 71 | hiddenName: 'templates', 72 | displayField: 'templatename', 73 | valueField: 'id', 74 | fields: ['templatename','id'], 75 | mode: 'remote', 76 | triggerAction: 'all', 77 | typeAhead: true, 78 | editable: true, 79 | forceSelection: true, 80 | pageSize: 20, 81 | url: MODx.config.connector_url, 82 | baseParams: { 83 | action: 'MODX\\Revolution\\Processors\\Element\\Template\\GetList' 84 | } 85 | }); 86 | Ext.applyIf(config,{ 87 | store: new Ext.data.JsonStore({ 88 | url: config.url, 89 | root: 'results', 90 | totalProperty: 'total', 91 | fields: config.fields, 92 | errorReader: MODx.util.JSONReader, 93 | baseParams: config.baseParams || {}, 94 | remoteSort: config.remoteSort || false, 95 | autoDestroy: true 96 | }) 97 | }); 98 | if (getStore === true) { 99 | config.store.load(); 100 | return config.store; 101 | } 102 | tagger.combo.Templates.superclass.constructor.call(this,config); 103 | this.config = config; 104 | return this; 105 | }; 106 | Ext.extend(tagger.combo.Templates,Ext.ux.form.SuperBoxSelect); 107 | Ext.reg('tagger-combo-templates',tagger.combo.Templates); 108 | 109 | tagger.combo.TV = function(config) { 110 | config = config || {}; 111 | Ext.applyIf(config,{ 112 | name: 'tv', 113 | hiddenName: 'tv', 114 | displayField: 'name', 115 | valueField: 'id', 116 | fields: ['name','id'], 117 | pageSize: 20, 118 | url: MODx.config.connector_url, 119 | baseParams:{ 120 | action: 'Tagger\\Processors\\Extra\\GetTVs' 121 | } 122 | }); 123 | tagger.combo.TV.superclass.constructor.call(this,config); 124 | }; 125 | Ext.extend(tagger.combo.TV,MODx.combo.ComboBox); 126 | Ext.reg('tagger-combo-tv',tagger.combo.TV); 127 | 128 | tagger.combo.GroupPlace = function(config) { 129 | config = config || {}; 130 | Ext.applyIf(config,{ 131 | store: new Ext.data.SimpleStore({ 132 | fields: ['d','v'], 133 | data: [ 134 | [_('tagger.group.place_in_tab') ,'in-tab'], 135 | [_('tagger.group.place_tvs_tab') ,'in-tvs'], 136 | [_('tagger.group.place_bottom_page') ,'bottom-page'] 137 | ] 138 | }), 139 | displayField: 'd', 140 | valueField: 'v', 141 | mode: 'local', 142 | value: 'in-tab', 143 | triggerAction: 'all', 144 | editable: false, 145 | selectOnFocus: false, 146 | preventRender: true, 147 | forceSelection: true, 148 | enableKeyEvents: true 149 | }); 150 | tagger.combo.GroupPlace.superclass.constructor.call(this,config); 151 | }; 152 | Ext.extend(tagger.combo.GroupPlace,MODx.combo.ComboBox); 153 | Ext.reg('tagger-combo-group-place',tagger.combo.GroupPlace); 154 | 155 | tagger.combo.SortDir = function(config) { 156 | config = config || {}; 157 | Ext.applyIf(config,{ 158 | store: new Ext.data.SimpleStore({ 159 | fields: ['d','v'], 160 | data: [ 161 | [_('tagger.group.sort_asc') ,'asc'], 162 | [_('tagger.group.sort_desc') ,'desc'] 163 | ] 164 | }), 165 | displayField: 'd', 166 | valueField: 'v', 167 | mode: 'local', 168 | value: 'asc', 169 | triggerAction: 'all', 170 | editable: false, 171 | selectOnFocus: false, 172 | preventRender: true, 173 | forceSelection: true, 174 | enableKeyEvents: true 175 | }); 176 | tagger.combo.SortDir.superclass.constructor.call(this,config); 177 | }; 178 | Ext.extend(tagger.combo.SortDir,MODx.combo.ComboBox); 179 | Ext.reg('tagger-combo-sort-dir',tagger.combo.SortDir); 180 | 181 | tagger.combo.SortField = function(config) { 182 | config = config || {}; 183 | Ext.applyIf(config,{ 184 | store: new Ext.data.SimpleStore({ 185 | fields: ['d','v'], 186 | data: [ 187 | [_('tagger.group.sort_field_alias') ,'alias'], 188 | [_('tagger.group.sort_field_rank') ,'rank'] 189 | ] 190 | }), 191 | displayField: 'd', 192 | valueField: 'v', 193 | mode: 'local', 194 | value: 'alias', 195 | triggerAction: 'all', 196 | editable: false, 197 | selectOnFocus: false, 198 | preventRender: true, 199 | forceSelection: true, 200 | enableKeyEvents: true 201 | }); 202 | tagger.combo.SortField.superclass.constructor.call(this,config); 203 | }; 204 | Ext.extend(tagger.combo.SortField,MODx.combo.ComboBox); 205 | Ext.reg('tagger-combo-sort-field',tagger.combo.SortField); 206 | -------------------------------------------------------------------------------- /core/components/tagger/lexicon/fr/default.inc.php: -------------------------------------------------------------------------------- 1 | oui pour que tous les tags qui ne sont pas assignés à une ressource soient supprimés de la base de données.'; 31 | $_lang['tagger.group.allow_new'] = 'Autoriser la création de tag depuis le champ'; 32 | $_lang['tagger.group.allow_new_desc'] = 'Sélectionnez oui pour que les utilisateurs puissent créer de nouveaux tags depuis le champ. Désactiver cette option peut être utile pour, par exemple, définir une liste de catégories fixe'; 33 | $_lang['tagger.group.allow_blank'] = 'Optionnel'; 34 | $_lang['tagger.group.allow_blank_desc'] = 'Sélectionnez non pour que le champ soit obligatoire (les utilisateurs devront sélectionner au moins un tag avant de pouvoir enregistrer la ressource)'; 35 | $_lang['tagger.group.allow_type'] = 'Saisie autorisée'; 36 | $_lang['tagger.group.allow_type_desc'] = 'Sélectionnez non pour empêcher la saisie au clavier. Cliquer sur le champ entrainera l\'affichage des tags disponibles.'; 37 | $_lang['tagger.group.show_autotag'] = 'Afficher les tags'; 38 | $_lang['tagger.group.show_autotag_desc'] = 'Sélectionnez oui pour que les tags soient affichés en dessous du champ. Les utilisateurs pourront cliquer les les tags pour les sélectionner/déselectionner. Disponible uniquement pour les listes de tags.'; 39 | $_lang['tagger.group.show_for_templates'] = 'Afficher pour les templates'; 40 | $_lang['tagger.group.show_for_templates_desc'] = 'Liste, séparée par des virgules, d\'IDs de templates pour lesquels ce groupe sera affiché.'; 41 | $_lang['tagger.group.position'] = 'Position'; 42 | $_lang['tagger.group.all'] = 'Tous les groupes'; 43 | $_lang['tagger.group.create'] = 'Créer un groupe'; 44 | $_lang['tagger.group.update'] = 'Mettre à jour'; 45 | $_lang['tagger.group.remove'] = 'Supprimer'; 46 | $_lang['tagger.group.remove_confirm'] = 'Êtes-vous sûr de vouloir supprimer ce groupe ? Tous les tags du groupe seront également supprimés.'; 47 | $_lang['tagger.group.import'] = 'Import'; 48 | $_lang['tagger.group.auto_import'] = 'Import automatique'; 49 | $_lang['tagger.group.import_from'] = 'Import depuis une TV'; 50 | $_lang['tagger.group.import_to'] = 'Importer dans le groupe'; 51 | $_lang['tagger.group.place'] = 'Place'; 52 | $_lang['tagger.group.place_desc'] = 'Où sera affiché ce groupe ? Les choix sont : Un onglet, Au dessus du contenu, En dessous du contenu, En pied de page'; 53 | $_lang['tagger.group.place_in_tab'] = 'Un onglet'; 54 | $_lang['tagger.group.place_above_content'] = 'Au dessus du contenu'; 55 | $_lang['tagger.group.place_below_content'] = 'En dessous du contenu'; 56 | $_lang['tagger.group.place_bottom_page'] = 'En pied de page'; 57 | $_lang['tagger.group.hide_input'] = 'Hide input'; 58 | $_lang['tagger.group.hide_input_desc'] = 'When checked the input field with assign button will be hidden.'; 59 | $_lang['tagger.group.tag_limit'] = 'Limite'; 60 | $_lang['tagger.group.tag_limit_desc'] = 'Nombre de tags pouvant être assignés à une ressource. 0 = illimité'; 61 | $_lang['tagger.group.alias'] = 'Alias'; 62 | $_lang['tagger.group.alias_desc'] = 'Alias qui sera utilisé dans les URLs lorsque les FURLs sont activées. Laissez vide pour générer l\'alias automatiquement.'; 63 | 64 | $_lang['tagger.tab.label'] = 'Tags'; 65 | 66 | $_lang['tagger.tag.tags'] = 'Tags'; 67 | $_lang['tagger.tag.intro_msg'] = 'Dans cette section, vous pouvez gérer les tags.'; 68 | $_lang['tagger.tag.name'] = 'Nom'; 69 | $_lang['tagger.tag.alias'] = 'Alias'; 70 | $_lang['tagger.tag.group'] = 'Groupe'; 71 | $_lang['tagger.tag.create'] = 'Créer un tag'; 72 | $_lang['tagger.tag.update'] = 'Mettre à jour'; 73 | $_lang['tagger.tag.remove'] = 'Supprimer'; 74 | $_lang['tagger.tag.remove_confirm'] = 'Êtes-vous sûr de vouloir supprimer ce tag ?'; 75 | $_lang['tagger.tag.assigned_resources'] = 'Ressources assignées/tagguées'; 76 | $_lang['tagger.tag.assigned_resources_to'] = 'Ressources assignées au tag : [[+tag]]'; 77 | $_lang['tagger.tag.resource_update'] = 'Mettre à jour la ressource'; 78 | $_lang['tagger.tag.resource_unassign'] = 'Dé-tagguer la ressource'; 79 | $_lang['tagger.tag.resource_unassign_confirm'] = 'Êtes-vous sûr de vouloir supprimer ce tag de la ressource ?'; 80 | $_lang['tagger.tag.resource_unassign_multiple_confirm'] = 'Êtes-vous sûr de vouloir supprimer ce tag des ([[+resources]]) ressources sélectionnées ?'; 81 | $_lang['tagger.tag.resource_unasign_selected'] = 'Dé-tagguer la sélection'; 82 | $_lang['tagger.tag.bulk_actions'] = 'Actions de groupe'; 83 | $_lang['tagger.tag.merge_selected'] = 'Fusionner les tags sélectionnés'; 84 | $_lang['tagger.tag.remove_selected'] = 'Supprimer les tags sélectionnés'; 85 | $_lang['tagger.tag.remove_selected_confirm'] = 'Êtes-vous sûr de vouloir supprimer tous les tags sélectionnés ?'; 86 | $_lang['tagger.tag.merge'] = 'Fusionner les tags'; 87 | $_lang['tagger.tag.assign'] = 'Assigner'; 88 | 89 | $_lang['setting_tagger.tag_key'] = 'Variable de tag'; 90 | $_lang['setting_tagger.tag_key_desc'] = 'Nom de la variable GET utilisée pour stocker les tags. Défaut: tags'; 91 | $_lang['setting_tagger.place_above_content_header'] = 'Entête "Au dessus"'; 92 | $_lang['setting_tagger.place_above_content_header_desc'] = 'Affiche ou masque l\'entête pour le bloc "Au dessus du contenu".'; 93 | $_lang['setting_tagger.place_below_content_header'] = 'Entête "En dessous"'; 94 | $_lang['setting_tagger.place_below_content_header_desc'] = 'Affiche ou masque l\'entête pour le bloc "En dessous du contenu".'; 95 | $_lang['setting_tagger.place_bottom_page_header'] = 'Entête "Pied de page"'; 96 | $_lang['setting_tagger.place_bottom_page_header_desc'] = 'Affiche ou masque l\'entête pour le bloc "Pied de page".'; 97 | 98 | $_lang['area_places'] = 'Places'; 99 | $_lang['area_default'] = 'Défault'; 100 | 101 | $_lang['tagger.err.group_name_ns'] = 'Veuillez indiquer un nom de groupe.'; 102 | $_lang['tagger.err.group_name_ae'] = 'Groupe existant, veuillez indiquer un autre nom.'; 103 | $_lang['tagger.err.tag_name_ns'] = 'Veuillez indiquer un nom de tag.'; 104 | $_lang['tagger.err.tag_name_ae'] = 'Tag existant dans le groupe, veuillez indiquer un nom de tag différent ou un autre groupe.'; 105 | $_lang['tagger.err.tag_group_changed'] = 'Le groupe du tag ne peut être changé.'; 106 | $_lang['tagger.err.bad_sort_column'] = 'Utiliser [[+column]] pour le classement par drag & drop dans la grille.'; 107 | $_lang['tagger.err.clear_filter'] = 'Veuillez effacer la recherche afin de pouvoir utiliser le classement par drag & drop.'; 108 | $_lang['tagger.err.import_group_ns'] = 'Groupe non indiqué, veuillez sélectionner un groupe.'; 109 | $_lang['tagger.err.import_tv_ns'] = 'TV non indiquée, veuillez sélectionner une TV.'; 110 | $_lang['tagger.err.import_tv_ne'] = 'TV non trouvée, veuillez corriger.'; 111 | $_lang['tagger.err.import_tv_nsp'] = 'Le type de TV sélctionné n\'est pas supporté. Les types supportés sont : [[+supported]]'; 112 | $_lang['tagger.err.tag_assigned_resources_tag_ns'] = 'Tag non défini, veuillez recommencer.'; 113 | $_lang['tagger.err.tags_ns'] = 'Tags non définis.'; 114 | $_lang['tagger.err.merge_same_groups'] = 'Vous ne pouvez pas fusionner ces tags'; 115 | $_lang['tagger.err.merge_same_groups_desc'] = 'Les tags sélectionnés ne peuvent être fusionnés car ils appartiennent à des groupes différents.'; 116 | $_lang['tagger.err.tag_alias_ae'] = 'Un tag utilisant cet alias existe déjà dans ce groupe, veuillez indiquer un autre alias ou sélectionner un autre groupe.'; 117 | $_lang['tagger.err.group_alias_ae'] = 'Un groupe utilisant cet alias existe déjà, veuillez indiquer un autre alias.'; 118 | -------------------------------------------------------------------------------- /core/components/tagger/lexicon/en/default.inc.php: -------------------------------------------------------------------------------- 1 | yes, all tags, that are not assigned to at least one resource will be removed from database.'; 31 | $_lang['tagger.group.allow_new'] = 'Allow new tags from field'; 32 | $_lang['tagger.group.allow_new_desc'] = 'If set to yes, users will be able to add new tags from field. Setting to no can be useful for example for list of categories'; 33 | $_lang['tagger.group.allow_blank'] = 'Allow blank'; 34 | $_lang['tagger.group.allow_blank_desc'] = 'If set to no, field will be marked as required and users will have to select at least on tag before saving the Resource'; 35 | $_lang['tagger.group.allow_type'] = 'Allow type'; 36 | $_lang['tagger.group.allow_type_desc'] = 'If set to no, users will not be able to type in the field. Clicking to the field will trigger showing list of available tags.'; 37 | $_lang['tagger.group.show_autotag'] = 'Show autotag'; 38 | $_lang['tagger.group.show_autotag_desc'] = 'If set to yes, all tags will be displayed below the field. Users will be bale to click on them to select/unselect. Available only for Tag field.'; 39 | $_lang['tagger.group.show_for_templates'] = 'Show for Templates'; 40 | $_lang['tagger.group.show_for_templates_desc'] = 'Comma separated list of template IDs for that should be this group displayed.'; 41 | $_lang['tagger.group.position'] = 'Position'; 42 | $_lang['tagger.group.all'] = 'All groups'; 43 | $_lang['tagger.group.create'] = 'Create a new Group'; 44 | $_lang['tagger.group.update'] = 'Update Group'; 45 | $_lang['tagger.group.remove'] = 'Remove Group'; 46 | $_lang['tagger.group.remove_confirm'] = 'Are you sure, you want to remove this Group? All tags in this group will be also removed.'; 47 | $_lang['tagger.group.import'] = 'Import'; 48 | $_lang['tagger.group.auto_import'] = 'Automatic import'; 49 | $_lang['tagger.group.import_from'] = 'Import from TV'; 50 | $_lang['tagger.group.import_to'] = 'Import to Group'; 51 | $_lang['tagger.group.place'] = 'Place'; 52 | $_lang['tagger.group.place_desc'] = 'Where will be this group rendered. Options are: In tab, In TVs section, Above content, Below content, Bottom page'; 53 | $_lang['tagger.group.place_in_tab'] = 'In tab'; 54 | $_lang['tagger.group.place_tvs_tab'] = 'In TVs section'; 55 | $_lang['tagger.group.place_bottom_page'] = 'Bottom page'; 56 | $_lang['tagger.group.hide_input'] = 'Hide input'; 57 | $_lang['tagger.group.hide_input_desc'] = 'When checked the input field with assign button will be hidden.'; 58 | $_lang['tagger.group.tag_limit'] = 'Tag limit'; 59 | $_lang['tagger.group.tag_limit_desc'] = 'Number of tags that can be assigned to a resource'; 60 | $_lang['tagger.group.alias'] = 'Alias'; 61 | $_lang['tagger.group.alias_desc'] = 'Alias that will be used in URL when FURLs are enabled. Leave field blank to generate alias automatically.'; 62 | $_lang['tagger.group.in_tvs_position'] = 'Position of Tagger tab in TVs section'; 63 | $_lang['tagger.group.in_tvs_position_desc'] = 'Position in TVs section on which will be added Tagger tab'; 64 | $_lang['tagger.group.as_radio'] = 'As Radio'; 65 | $_lang['tagger.group.as_radio_desc'] = 'Auto tag will act like radio button.'; 66 | $_lang['tagger.group.sort_asc'] = 'ASC'; 67 | $_lang['tagger.group.sort_desc'] = 'DESC'; 68 | $_lang['tagger.group.sort_dir'] = 'Sort Dir'; 69 | $_lang['tagger.group.sort_dir_desc'] = 'Sort direction - direction of sorting when fetching tags.'; 70 | $_lang['tagger.group.sort_field'] = 'Sort Field'; 71 | $_lang['tagger.group.sort_field_desc'] = 'Sort field - field used for sorting Tags'; 72 | $_lang['tagger.group.sort_field_alias'] = 'Alias'; 73 | $_lang['tagger.group.sort_field_rank'] = 'Rank'; 74 | $_lang['tagger.group.show_for_contexts'] = 'Show for Contexts'; 75 | $_lang['tagger.group.show_for_contexts_desc'] = 'Comma separated list of context KEYs for that should be this group displayed. By default the group will appear for all contexts.'; 76 | 77 | $_lang['tagger.tab.label'] = 'Tagger'; 78 | 79 | $_lang['tagger.tag.tags'] = 'Tags'; 80 | $_lang['tagger.tag.intro_msg'] = 'In this section you can manage Tags.'; 81 | $_lang['tagger.tag.name'] = 'Name'; 82 | $_lang['tagger.tag.alias'] = 'Alias'; 83 | $_lang['tagger.tag.group'] = 'Group'; 84 | $_lang['tagger.tag.create'] = 'Create a new Tag'; 85 | $_lang['tagger.tag.update'] = 'Update Tag'; 86 | $_lang['tagger.tag.remove'] = 'Remove Tag'; 87 | $_lang['tagger.tag.remove_confirm'] = 'Are you sure, you want to remove this Tag?'; 88 | $_lang['tagger.tag.assigned_resources'] = 'Assigned resources'; 89 | $_lang['tagger.tag.assigned_resources_to'] = 'Assigned resources to [[+tag]]'; 90 | $_lang['tagger.tag.resource_update'] = 'Update resource'; 91 | $_lang['tagger.tag.resource_unassign'] = 'Unassign resource'; 92 | $_lang['tagger.tag.resource_unassign_confirm'] = 'Are you sure, you want to unassign this resource?'; 93 | $_lang['tagger.tag.resource_unassign_multiple_confirm'] = 'Are you sure, you want to unassign selected ([[+resources]]) resources?'; 94 | $_lang['tagger.tag.resource_unasign_selected'] = 'Unassign selected'; 95 | $_lang['tagger.tag.bulk_actions'] = 'Bulk actions'; 96 | $_lang['tagger.tag.merge_selected'] = 'Merge selected Tags'; 97 | $_lang['tagger.tag.remove_selected'] = 'Remove selected Tags'; 98 | $_lang['tagger.tag.remove_selected_confirm'] = 'Are you sure, you want to remove all selected Tags?'; 99 | $_lang['tagger.tag.merge'] = 'Merge Tags'; 100 | $_lang['tagger.tag.assign'] = 'Assign'; 101 | $_lang['tagger.tag.rank'] = 'Rank'; 102 | $_lang['tagger.tag.label'] = 'Label'; 103 | 104 | $_lang['setting_tagger.place_bottom_page_header'] = 'Bottom page header'; 105 | $_lang['setting_tagger.place_bottom_page_header_desc'] = 'Show or hide header of the Tagger block, that shows at the bottom of the page.'; 106 | $_lang['setting_tagger.place_in_tab_label'] = 'In tab label'; 107 | $_lang['setting_tagger.place_in_tab_label_desc'] = 'Label of the Tagger block, that shows in the tab.'; 108 | $_lang['setting_tagger.place_tvs_tab_label'] = 'In TVs section label'; 109 | $_lang['setting_tagger.place_tvs_tab_label_desc'] = 'Label of the Tagger block, that shows in the TVs section.'; 110 | $_lang['setting_tagger.place_bottom_page_label'] = 'Bottom page label'; 111 | $_lang['setting_tagger.place_bottom_page_label_desc'] = 'Label of the Tagger block, that shows at the bottom of the page.'; 112 | $_lang['setting_tagger.remove_accents_tag'] = 'Tags Remove Accents'; 113 | $_lang['setting_tagger.remove_accents_tag_desc'] = 'Remove accent characters from tag alias.'; 114 | $_lang['setting_tagger.remove_accents_group'] = 'Groups Remove Accents'; 115 | $_lang['setting_tagger.remove_accents_group_desc'] = 'Remove accent characters from group alias.'; 116 | 117 | 118 | $_lang['area_places'] = 'Places'; 119 | $_lang['area_default'] = 'Default'; 120 | $_lang['area_settings'] = 'Settings'; 121 | 122 | $_lang['tagger.err.group_name_ns'] = 'Group name is not specified. Please fill Group name.'; 123 | $_lang['tagger.err.group_name_ae'] = 'Group with this name already exists. Please choose a different name.'; 124 | $_lang['tagger.err.tag_name_ns'] = 'Tag name is not specified. Please fill Tag name.'; 125 | $_lang['tagger.err.tag_name_ae'] = 'Tag with this name already exists in given group. Please choose a different name or group.'; 126 | $_lang['tagger.err.tag_group_changed'] = 'Tag group can not be changed.'; 127 | $_lang['tagger.err.bad_sort_column'] = 'Sort grid by [[+column]] to use drag & drop sorting.'; 128 | $_lang['tagger.err.clear_filter'] = 'Please clear search to use drag & drop sorting.'; 129 | $_lang['tagger.err.import_group_ns'] = 'Group is not specified. Please select a Group.'; 130 | $_lang['tagger.err.import_tv_ns'] = 'Template Variable is not specified. Please select a TV.'; 131 | $_lang['tagger.err.import_tv_ne'] = 'Template Variable was not found. Please try to repeat your action.'; 132 | $_lang['tagger.err.import_tv_nsp'] = 'Type of selected Template Variable is not supported. Supported types are: [[+supported]]'; 133 | $_lang['tagger.err.tag_assigned_resources_tag_ns'] = 'Tag is not specified. Please try to repeat your action.'; 134 | $_lang['tagger.err.tags_ns'] = 'Tags are not specified.'; 135 | $_lang['tagger.err.merge_same_groups'] = 'You can\'t merge those Tags'; 136 | $_lang['tagger.err.merge_same_groups_desc'] = 'Selected Tags can not be merged, because they belongs to different groups.'; 137 | $_lang['tagger.err.tag_alias_ae'] = 'Tag with this alias already exists in given group. Please choose a different alias or group.'; 138 | $_lang['tagger.err.group_alias_ae'] = 'Group with this alias already exists. Please choose a different alias.'; 139 | -------------------------------------------------------------------------------- /assets/components/tagger/js/mgr/widgets/group/group.grid.js: -------------------------------------------------------------------------------- 1 | tagger.grid.Group = function(config) { 2 | config = config || {}; 3 | Ext.applyIf(config,{ 4 | id: 'tagger-grid-group', 5 | url: MODx.config.connector_url, 6 | baseParams: { 7 | action: 'Tagger\\Processors\\Group\\GetList' 8 | }, 9 | save_action: 'Tagger\\Processors\\Group\\UpdateFromGrid', 10 | autosave: true, 11 | fields: ['id', 'name', 'alias', 'field_type', 'remove_unused', 'allow_new', 'allow_blank', 'allow_type', 'show_autotag', 'hide_input', 'tag_limit', 'show_for_templates', 'position', 'place', 'description', 'in_tvs_position', 'as_radio', 'sort_field', 'sort_dir', 'show_for_contexts'], 12 | autoHeight: true, 13 | paging: true, 14 | remoteSort: true, 15 | ddGroup: 'TaggerDDGroup', 16 | enableDragDrop: true, 17 | columns: [{ 18 | header: _('id'), 19 | dataIndex: 'id', 20 | width: 40, 21 | sortable: true 22 | },{ 23 | header: _('tagger.group.name'), 24 | dataIndex: 'name', 25 | width: 150, 26 | sortable: true, 27 | editor: { xtype: 'textfield' } 28 | },{ 29 | header: _('tagger.group.alias'), 30 | dataIndex: 'alias', 31 | width: 150, 32 | sortable: true, 33 | editor: { xtype: 'textfield' } 34 | },{ 35 | header: _('tagger.group.field_type'), 36 | dataIndex: 'field_type', 37 | width: 100, 38 | sortable: true, 39 | editor: { xtype: 'tagger-combo-field-type', renderer: true } 40 | },{ 41 | header: _('tagger.group.remove_unused'), 42 | dataIndex: 'remove_unused', 43 | width: 150, 44 | sortable: true, 45 | renderer: this.rendYesNo, 46 | editor: { xtype: 'modx-combo-boolean' } 47 | },{ 48 | header: _('tagger.group.allow_new'), 49 | dataIndex: 'allow_new', 50 | width: 180, 51 | sortable: true, 52 | renderer: this.rendYesNo, 53 | editor: { xtype: 'modx-combo-boolean' } 54 | },{ 55 | header: _('tagger.group.allow_blank'), 56 | dataIndex: 'allow_blank', 57 | width: 200, 58 | sortable: true, 59 | renderer: this.rendYesNo, 60 | editor: { xtype: 'modx-combo-boolean' }, 61 | hidden: true 62 | },{ 63 | header: _('tagger.group.allow_type'), 64 | dataIndex: 'allow_type', 65 | width: 200, 66 | sortable: true, 67 | renderer: this.rendYesNo, 68 | editor: { xtype: 'modx-combo-boolean' }, 69 | hidden: true 70 | },{ 71 | header: _('tagger.group.show_autotag'), 72 | dataIndex: 'show_autotag', 73 | width: 200, 74 | sortable: true, 75 | renderer: this.rendYesNo, 76 | editor: { xtype: 'modx-combo-boolean' }, 77 | hidden: true 78 | },{ 79 | header: _('tagger.group.place'), 80 | dataIndex: 'place', 81 | width: 200, 82 | sortable: true, 83 | editor: { xtype: 'tagger-combo-group-place', renderer: true }, 84 | hidden: true 85 | },{ 86 | header: _('tagger.group.show_for_templates'), 87 | dataIndex: 'show_for_templates', 88 | width: 150 89 | },{ 90 | header: _('tagger.group.position'), 91 | dataIndex: 'position', 92 | width: 200, 93 | hidden: true, 94 | editor: {xtype: 'numberfield', allowDecimal: false, allowNegative: false} 95 | }], 96 | tbar: [{ 97 | text: _('tagger.group.create'), 98 | handler: this.createGroup, 99 | scope: this 100 | },'-',{ 101 | text: _('tagger.group.import'), 102 | handler: this.importToGroup, 103 | scope: this 104 | },'->',{ 105 | xtype: 'textfield', 106 | emptyText: _('tagger.global.search') + '...', 107 | listeners: { 108 | change: {fn:this.search,scope:this}, 109 | render: {fn: function(cmp) { 110 | new Ext.KeyMap(cmp.getEl(), { 111 | key: Ext.EventObject.ENTER, 112 | fn: function() { 113 | this.fireEvent('change',this); 114 | this.blur(); 115 | return true; 116 | } 117 | ,scope: cmp 118 | }); 119 | },scope:this} 120 | } 121 | }] 122 | }); 123 | tagger.grid.Group.superclass.constructor.call(this,config); 124 | 125 | this.on('render', this.registerGridDropTarget, this); 126 | }; 127 | Ext.extend(tagger.grid.Group,MODx.grid.Grid,{ 128 | windows: {}, 129 | 130 | getMenu: function() { 131 | var m = []; 132 | m.push({ 133 | text: _('tagger.group.update'), 134 | handler: this.updateGroup 135 | }); 136 | m.push('-'); 137 | m.push({ 138 | text: _('tagger.group.remove'), 139 | handler: this.removeGroup 140 | }); 141 | this.addContextMenuItem(m); 142 | }, 143 | 144 | importToGroup: function(btn,e) { 145 | var importToGroup = MODx.load({ 146 | xtype: 'tagger-window-group-import', 147 | grid: this, 148 | listeners: { 149 | success: {fn:function() { this.refresh(); },scope:this} 150 | } 151 | }); 152 | 153 | importToGroup.show(e.target); 154 | }, 155 | 156 | createGroup: function(btn,e) { 157 | var createGroup = MODx.load({ 158 | xtype: 'tagger-window-group', 159 | title: _('tagger.group.create'), 160 | listeners: { 161 | 'success': {fn:function() { this.refresh(); },scope:this} 162 | } 163 | }); 164 | 165 | createGroup.show(e.target); 166 | }, 167 | 168 | updateGroup: function(btn,e) { 169 | var updateGroup = MODx.load({ 170 | xtype: 'tagger-window-group', 171 | title: _('tagger.group.update'), 172 | action: 'Tagger\\Processors\\Group\\Update', 173 | record: this.menu.record, 174 | listeners: { 175 | success: {fn:function() { this.refresh(); },scope:this} 176 | } 177 | }); 178 | 179 | updateGroup.fp.getForm().setValues(this.menu.record); 180 | updateGroup.show(e.target); 181 | }, 182 | 183 | removeGroup: function(btn,e) { 184 | if (!this.menu.record) return false; 185 | 186 | MODx.msg.confirm({ 187 | title: _('tagger.group.remove'), 188 | text: _('tagger.group.remove_confirm'), 189 | url: this.config.url, 190 | params: { 191 | action: 'Tagger\\Processors\\Group\\Remove', 192 | id: this.menu.record.id 193 | }, 194 | listeners: { 195 | success: {fn:function(r) { this.refresh(); },scope:this} 196 | } 197 | }); 198 | }, 199 | 200 | search: function(tf,nv,ov) { 201 | var s = this.getStore(); 202 | s.baseParams.query = tf.getValue(); 203 | this.getBottomToolbar().changePage(1); 204 | this.refresh(); 205 | }, 206 | 207 | getDragDropText: function(){ 208 | if (this.store.sortInfo && this.store.sortInfo.field != 'position') { 209 | return _('tagger.err.bad_sort_column', {column: 'position'}); 210 | } 211 | 212 | var search = this.getStore().baseParams.query; 213 | if (search && search != '') { 214 | return _('tagger.err.clear_filter'); 215 | } 216 | 217 | return _('tagger.global.change_order', {name: this.selModel.selections.items[0].data.name}); 218 | }, 219 | 220 | registerGridDropTarget: function() { 221 | 222 | var ddrow = new Ext.ux.dd.GridReorderDropTarget(this, { 223 | copy: false, 224 | sortCol: 'position', 225 | listeners: { 226 | beforerowmove: function(objThis, oldIndex, newIndex, records) { 227 | }, 228 | 229 | afterrowmove: function(objThis, oldIndex, newIndex, records) { 230 | MODx.Ajax.request({ 231 | url: MODx.config.connector_url, 232 | params: { 233 | action: 'Tagger\\Processors\\Group\\DDReorder', 234 | idGroup: records.pop().id, 235 | oldIndex: oldIndex, 236 | newIndex: newIndex 237 | }, 238 | listeners: { 239 | success: { 240 | fn: function(r) { 241 | this.target.grid.refresh(); 242 | },scope: this 243 | } 244 | } 245 | }); 246 | }, 247 | 248 | beforerowcopy: function(objThis, oldIndex, newIndex, records) { 249 | }, 250 | 251 | afterrowcopy: function(objThis, oldIndex, newIndex, records) { 252 | } 253 | } 254 | }); 255 | 256 | Ext.dd.ScrollManager.register(this.getView().getEditorParent()); 257 | }, 258 | 259 | destroyScrollManager: function() { 260 | Ext.dd.ScrollManager.unregister(this.getView().getEditorParent()); 261 | } 262 | }); 263 | Ext.reg('tagger-grid-group',tagger.grid.Group); 264 | 265 | 266 | -------------------------------------------------------------------------------- /core/components/tagger/lexicon/ru/default.inc.php: -------------------------------------------------------------------------------- 1 | Да, все тэги, которые не привязаны хотя бы к одному ресурсу, будут удалены.'; 31 | $_lang['tagger.group.allow_new'] = 'Разрешать создание новых'; 32 | $_lang['tagger.group.allow_new_desc'] = 'Если установлено в да, пользователи смогут создавать новые тэги при редактировании ресурсов. Установите в нет, чтобы заблокировать создание новых тэгов при редактировании ресурсов'; 33 | $_lang['tagger.group.allow_blank'] = 'Разрешать пустые'; 34 | $_lang['tagger.group.allow_blank_desc'] = 'Если установлено в нет, поле будет отмечено как обязательное, и пользователи не смогут сохранить ресурс, пока не укажут хотя бы один тэг'; 35 | $_lang['tagger.group.allow_type'] = 'Разрешенные типы'; 36 | $_lang['tagger.group.allow_type_desc'] = 'Если установлено в нет, пользователям не доступен ввод в это поле. Щелчок по полю отобразит список доступных тэгов.'; 37 | $_lang['tagger.group.show_autotag'] = 'Показывать автотэги'; 38 | $_lang['tagger.group.show_autotag_desc'] = 'Если установлено в да, все тэги будут показаны ниже поля. Пользователям необходимо будет щелкнуть на них для выбора / снятия выбора. Доступно только для поля с тэгами.'; 39 | $_lang['tagger.group.show_for_templates'] = 'Показывать для шаблонов'; 40 | $_lang['tagger.group.show_for_templates_desc'] = 'Разделенный запятыми список IDs шаблонов, которым доступна эта группа.'; 41 | $_lang['tagger.group.position'] = 'Позиция'; 42 | $_lang['tagger.group.all'] = 'Все группы'; 43 | $_lang['tagger.group.create'] = 'Создать новую группу'; 44 | $_lang['tagger.group.update'] = 'Обновить группу'; 45 | $_lang['tagger.group.remove'] = 'Удалить группу'; 46 | $_lang['tagger.group.remove_confirm'] = 'Вы уверены, что хотите удалить группу? Все тэги этой группы тоже будут удалены.'; 47 | $_lang['tagger.group.import'] = 'Импорт'; 48 | $_lang['tagger.group.auto_import'] = 'Автоматический импорт'; 49 | $_lang['tagger.group.import_from'] = 'Импорт из TV'; 50 | $_lang['tagger.group.import_to'] = 'Импорт в группу'; 51 | $_lang['tagger.group.place'] = 'Место'; 52 | $_lang['tagger.group.place_desc'] = 'Расположение Tagger при редактированни ресурса. Варианты: В отдельной вкладке, Во вкладке Доп. параметров, Выше содержимого, Ниже содержимого, В самом низу страницы'; 53 | $_lang['tagger.group.place_in_tab'] = 'В отдельной вкладке'; 54 | $_lang['tagger.group.place_tvs_tab'] = 'Во вкладке Доп. параметров'; 55 | $_lang['tagger.group.place_above_content'] = 'Над содержимым'; 56 | $_lang['tagger.group.place_below_content'] = 'Ниже содержимого'; 57 | $_lang['tagger.group.place_bottom_page'] = 'В самом низу страницы'; 58 | $_lang['tagger.group.hide_input'] = 'Спрятать поле ввода'; 59 | $_lang['tagger.group.hide_input_desc'] = 'Если отмечено, поле ввода с привязанной кнопкой будет спрятано.'; 60 | $_lang['tagger.group.tag_limit'] = 'Лимит тэгов'; 61 | $_lang['tagger.group.tag_limit_desc'] = 'Количество тэгов, которое может быть присвоено ресурсу'; 62 | $_lang['tagger.group.alias'] = 'Псевдоним'; 63 | $_lang['tagger.group.alias_desc'] = 'Псевдоним, который будет использован при включенных дружественных адресах. Оставьте пустым для автоматического создания.'; 64 | $_lang['tagger.group.in_tvs_position'] = 'Расположение Tagger на вкладке Доп. параметров'; 65 | $_lang['tagger.group.in_tvs_position_desc'] = 'Расположение Tagger на вкладке Доп. параметров'; 66 | 67 | $_lang['tagger.tab.label'] = 'Tagger'; 68 | 69 | $_lang['tagger.tag.tags'] = 'Тэги'; 70 | $_lang['tagger.tag.intro_msg'] = 'Вкладка управления тэгами'; 71 | $_lang['tagger.tag.name'] = 'Название'; 72 | $_lang['tagger.tag.alias'] = 'Псевдоним'; 73 | $_lang['tagger.tag.group'] = 'Группа'; 74 | $_lang['tagger.tag.create'] = 'Создать новый тэг'; 75 | $_lang['tagger.tag.update'] = 'Обновить тэг'; 76 | $_lang['tagger.tag.remove'] = 'Удалить тэг'; 77 | $_lang['tagger.tag.remove_confirm'] = 'Вы уверены, что хотите удалить этот тэг?'; 78 | $_lang['tagger.tag.assigned_resources'] = 'Привязанные ресурсы'; 79 | $_lang['tagger.tag.assigned_resources_to'] = 'Привязанные ресурсы к тэгу [[+tag]]'; 80 | $_lang['tagger.tag.resource_update'] = 'Обновить ресурс'; 81 | $_lang['tagger.tag.resource_unassign'] = 'Отвязать ресурс'; 82 | $_lang['tagger.tag.resource_unassign_confirm'] = 'Вы уверены, что хотите отвязать этот ресурс?'; 83 | $_lang['tagger.tag.resource_unassign_multiple_confirm'] = 'Вы уверены, что хотите отвязать эти ([[+resources]]) ресурсы?'; 84 | $_lang['tagger.tag.resource_unasign_selected'] = 'Выбранные отвязаны'; 85 | $_lang['tagger.tag.bulk_actions'] = 'Набор действий'; 86 | $_lang['tagger.tag.merge_selected'] = 'Соединить выбранные тэги'; 87 | $_lang['tagger.tag.remove_selected'] = 'Удалить выбранные тэги'; 88 | $_lang['tagger.tag.remove_selected_confirm'] = 'Вы уверены, что хотите удалить все выбранные тэги?'; 89 | $_lang['tagger.tag.merge'] = 'Соединить тэги'; 90 | $_lang['tagger.tag.assign'] = 'Привязать'; 91 | 92 | $_lang['setting_tagger.place_above_content_header'] = 'Заголовок, если выше содержимого'; 93 | $_lang['setting_tagger.place_above_content_header_desc'] = 'Показать или спрятать заголовок блока тэгов, который отображается выше содержимого.'; 94 | $_lang['setting_tagger.place_below_content_header'] = 'Заголовок, если ниже содержимого'; 95 | $_lang['setting_tagger.place_below_content_header_desc'] = 'Показать или спрятать заголовок блока тэгов, который отображается ниже содержимого.'; 96 | $_lang['setting_tagger.place_bottom_page_header'] = 'Заголовок, если в самом низу страницы'; 97 | $_lang['setting_tagger.place_bottom_page_header_desc'] = 'Показать или спрятать заголовок блока тэгов, который отображается в самом низу страницы.'; 98 | $_lang['setting_tagger.place_in_tab_label'] = 'Заголовок, если в отдельной вкладке'; 99 | $_lang['setting_tagger.place_in_tab_label_desc'] = 'Заголовок блока Tagger, который отображается на отдельной вкладке.'; 100 | $_lang['setting_tagger.place_tvs_tab_label'] = 'Заголовок, если во вкладке Доп. параметров'; 101 | $_lang['setting_tagger.place_tvs_tab_label_desc'] = 'Заголовок блока Tagger, который отображается на вкладке Доп. параметров.'; 102 | $_lang['setting_tagger.place_above_content_label'] = 'Метка, если выше содержимого'; 103 | $_lang['setting_tagger.place_above_content_label_desc'] = 'Заголовок блока Tagger, который отображается выше содержимого ресурса.'; 104 | $_lang['setting_tagger.place_below_content_label'] = 'Метка, если ниже содержимого'; 105 | $_lang['setting_tagger.place_below_content_label_desc'] = 'Заголовок блока Tagger, который отображается ниже содержимого ресурса.'; 106 | $_lang['setting_tagger.place_bottom_page_label'] = 'Метка, если в самом низу страницы'; 107 | $_lang['setting_tagger.place_bottom_page_label_desc'] = 'Заголовок блока Tagger, который отображается в самом низу страницы.'; 108 | 109 | $_lang['area_places'] = 'Места'; 110 | $_lang['area_default'] = 'По умолчанию'; 111 | 112 | $_lang['tagger.err.group_name_ns'] = 'Название группы не заполнено. Пожалуйста, укажите название группы'; 113 | $_lang['tagger.err.group_name_ae'] = 'Такое название группы уже существует. Пожалуйста, выберите другое название.'; 114 | $_lang['tagger.err.tag_name_ns'] = 'Название тэга не заполнено. Пожалуйста, укажите название тэга'; 115 | $_lang['tagger.err.tag_name_ae'] = 'Тэг с таким названием уже существует в этой группе. Пожалуйста, выберите другое название или группу.'; 116 | $_lang['tagger.err.tag_group_changed'] = 'Группа тэга не может быть изменена.'; 117 | $_lang['tagger.err.bad_sort_column'] = 'Отсортируйте таблицу по столбцу [[+column]] для использования drag & drop сортировки.'; 118 | $_lang['tagger.err.clear_filter'] = 'Пожалуйста, очистите поиск для сортировки перетаскиванием.'; 119 | $_lang['tagger.err.import_group_ns'] = 'Группа не указана. Пожалуйста, выберите группу.'; 120 | $_lang['tagger.err.import_tv_ns'] = 'Дополнительное поле не указано. Пожалуйста, выберите TV.'; 121 | $_lang['tagger.err.import_tv_ne'] = 'Дополнительное поле не существует. Пожалуйста, повторите попытку.'; 122 | $_lang['tagger.err.import_tv_nsp'] = 'Тип выбранного TV не поддерживается. Поддерживаемые типы: [[+supported]]'; 123 | $_lang['tagger.err.tag_assigned_resources_tag_ns'] = 'Тэг не указан. Пожалуйста, выполните действие еще раз.'; 124 | $_lang['tagger.err.tags_ns'] = 'Тэги не указаны.'; 125 | $_lang['tagger.err.merge_same_groups'] = 'Вы не можете соединить эти тэги'; 126 | $_lang['tagger.err.merge_same_groups_desc'] = 'Выбранные тэги не могут быть соединены, так как относятся к разным группам'; 127 | $_lang['tagger.err.tag_alias_ae'] = 'Тэг с таким псевдонимом уже существует в этой группе. Пожалуйста, выберите другой псевдоним или группу.'; 128 | $_lang['tagger.err.group_alias_ae'] = 'Такой псевдоним группы уже существует. Пожалуйста, выберите другой псевдоним.'; 129 | -------------------------------------------------------------------------------- /core/components/tagger/lexicon/de/default.inc.php: -------------------------------------------------------------------------------- 1 | Ja gesetzt wird, werden alle Tags, die nicht mindestens einer Ressource zugewiesen sind, aus der Datenbank entfernt.'; 26 | $_lang['tagger.group.allow_new'] = 'Neue Tags aus dem Feld zulassen'; 27 | $_lang['tagger.group.allow_new_desc'] = 'Wenn diese Option auf Ja gesetzt wird, können Benutzer neue Tags aus dem Feld hinzufügen. Die Einstellung "Nein" kann z. B. für die Liste der Kategorien nützlich sein'; 28 | $_lang['tagger.group.allow_blank'] = 'Leere Auswahl zulassen'; 29 | $_lang['tagger.group.allow_blank_desc'] = 'Wenn diese Option auf Nein gesetzt wird, wird das Feld als erforderlich markiert und die Benutzer müssen mindestens einen Tag auswählen, bevor sie die Ressource speichern können'; 30 | $_lang['tagger.group.allow_type'] = 'Eingabe zulassen'; 31 | $_lang['tagger.group.allow_type_desc'] = 'Wenn diese Option auf Nein gesetzt wird, können die Benutzer keine eigenen Eingaben in das Feld machen. Wenn Sie auf das Feld klicken, wird eine Liste der verfügbaren Tags angezeigt.'; 32 | $_lang['tagger.group.show_autotag'] = 'Autotag anzeigen'; 33 | $_lang['tagger.group.show_autotag_desc'] = 'Wenn diese Option auf Ja gesetzt wird, werden alle Tags unterhalb des Feldes angezeigt. Die Benutzer können sie anklicken, um sie auszuwählen oder ihre Auswahl aufzuheben. Nur für ein Tag Feld verfügbar.'; 34 | $_lang['tagger.group.show_for_templates'] = 'Für Templates anzeigen'; 35 | $_lang['tagger.group.show_for_templates_desc'] = 'Kommagetrennte Liste der Templates IDs, bei denen die diese Gruppe angezeigt werden soll.'; 36 | $_lang['tagger.group.position'] = 'Position'; 37 | $_lang['tagger.group.all'] = 'Alle Gruppen'; 38 | $_lang['tagger.group.create'] = 'Eine neue Gruppe erstellen'; 39 | $_lang['tagger.group.update'] = 'Gruppe bearbeiten'; 40 | $_lang['tagger.group.remove'] = 'Gruppe entfernen'; 41 | $_lang['tagger.group.remove_confirm'] = 'Sind Sie sicher, dass Sie diese Gruppe entfernen möchten? Alle Tags in dieser Gruppe werden ebenfalls entfernt.'; 42 | $_lang['tagger.group.import'] = 'Importieren'; 43 | $_lang['tagger.group.auto_import'] = 'Automatischer Import'; 44 | $_lang['tagger.group.import_from'] = 'Von TV importieren'; 45 | $_lang['tagger.group.import_to'] = 'In Gruppe importieren'; 46 | $_lang['tagger.group.place'] = 'Ort'; 47 | $_lang['tagger.group.place_desc'] = 'Ausgabeort dieser Gruppe. Mögliche Optionen: In einem Reiter, im TV-Bereich, oberhalb vom Inhalt, unterhalb vom Inhalt, am Ende der Seite'; 48 | $_lang['tagger.group.place_in_tab'] = 'Im Reiter'; 49 | $_lang['tagger.group.place_tvs_tab'] = 'im TV-Bereich'; 50 | $_lang['tagger.group.place_bottom_page'] = 'Am Ende der Seite'; 51 | $_lang['tagger.group.hide_input'] = 'Eingabe ausblenden'; 52 | $_lang['tagger.group.hide_input_desc'] = 'Wenn diese Option aktiviert ist, wird das Eingabefeld mit der Schaltfläche \'Zuweisen\' ausgeblendet.'; 53 | $_lang['tagger.group.tag_limit'] = 'Tag-Limit'; 54 | $_lang['tagger.group.tag_limit_desc'] = 'Anzahl der Tags, die einer Ressource zugewiesen werden können'; 55 | $_lang['tagger.group.alias'] = 'Alias'; 56 | $_lang['tagger.group.alias_desc'] = 'Alias, der in der URL verwendet wird, wenn FURLs aktiviert sind. Lassen Sie das Feld leer, um den Alias automatisch zu generieren.'; 57 | $_lang['tagger.group.in_tvs_position'] = 'Position des Reiters Tagger im TV-Bereich'; 58 | $_lang['tagger.group.in_tvs_position_desc'] = 'Position im TV-Bereich, an welcher der Reiter Tagger hinzugefügt wird'; 59 | $_lang['tagger.group.as_radio'] = 'Als Radio'; 60 | $_lang['tagger.group.as_radio_desc'] = 'Der automatische Tag funktioniert wie eine Radio-Schaltfläche.'; 61 | $_lang['tagger.group.sort_asc'] = 'Aufsteigend'; 62 | $_lang['tagger.group.sort_desc'] = 'Absteigend'; 63 | $_lang['tagger.group.sort_dir'] = 'Sortieren'; 64 | $_lang['tagger.group.sort_dir_desc'] = 'Sortierrichtung - Richtung der Sortierung beim Abrufen von Tags.'; 65 | $_lang['tagger.group.sort_field'] = 'Sortierungs Feld'; 66 | $_lang['tagger.group.sort_field_desc'] = 'Sortierfeld - Feld für die Sortierung von Tags'; 67 | $_lang['tagger.group.sort_field_alias'] = 'Alias'; 68 | $_lang['tagger.group.sort_field_rank'] = 'Rang'; 69 | $_lang['tagger.group.show_for_contexts'] = 'Für Kontexte anzeigen'; 70 | $_lang['tagger.group.show_for_contexts_desc'] = 'Kommagetrennte Liste der Kontexte KEYs, in der diese Gruppe angezeigt werden soll. Standardmäßig wird die Gruppe in allen Kontexten angezeigt.'; 71 | $_lang['tagger.tab.label'] = 'Tagger'; 72 | $_lang['tagger.tag.tags'] = 'Tags'; 73 | $_lang['tagger.tag.intro_msg'] = 'In diesem Bereich können Sie Tags verwalten.'; 74 | $_lang['tagger.tag.name'] = 'Name'; 75 | $_lang['tagger.tag.alias'] = 'Alias'; 76 | $_lang['tagger.tag.group'] = 'Gruppe'; 77 | $_lang['tagger.tag.create'] = 'Ein neues Tag erstellen'; 78 | $_lang['tagger.tag.update'] = 'Tag bearbeiten'; 79 | $_lang['tagger.tag.remove'] = 'Tag entfernen'; 80 | $_lang['tagger.tag.remove_confirm'] = 'Sind Sie sicher, dass Sie dieses Tag entfernen möchten?'; 81 | $_lang['tagger.tag.assigned_resources'] = 'Zugewiesene Ressourcen'; 82 | $_lang['tagger.tag.assigned_resources_to'] = 'Zugewiesene Ressourcen zu [[+tag]]'; 83 | $_lang['tagger.tag.resource_update'] = 'Ressource bearbeiten'; 84 | $_lang['tagger.tag.resource_unassign'] = 'Zuweisung aufheben'; 85 | $_lang['tagger.tag.resource_unassign_confirm'] = 'Sind Sie sicher, dass Sie die Zuweisung dieser Ressource aufheben möchten?'; 86 | $_lang['tagger.tag.resource_unassign_multiple_confirm'] = 'Sind Sie sicher, dass Sie die Zuweisung ausgewählter ([[+Ressourcen]]) Ressourcen aufheben wollen?'; 87 | $_lang['tagger.tag.resource_unasign_selected'] = 'Ausgewählte Zuweisung aufheben'; 88 | $_lang['tagger.tag.bulk_actions'] = 'Massen-Aktionen'; 89 | $_lang['tagger.tag.merge_selected'] = 'Ausgewählte Tags zusammenführen'; 90 | $_lang['tagger.tag.remove_selected'] = 'Ausgewählte Tags entfernen'; 91 | $_lang['tagger.tag.remove_selected_confirm'] = 'Sind Sie sicher, dass Sie alle ausgewählten Tags entfernen möchten?'; 92 | $_lang['tagger.tag.merge'] = 'Tags zusammenführen'; 93 | $_lang['tagger.tag.assign'] = 'Zuweisen'; 94 | $_lang['tagger.tag.rank'] = 'Rang'; 95 | $_lang['tagger.tag.label'] = 'Bezeichnung'; 96 | $_lang['setting_tagger.place_bottom_page_header'] = 'Kopfzeile am Ende der Seite'; 97 | $_lang['setting_tagger.place_bottom_page_header_desc'] = 'Ein- oder Ausblenden der Kopfzeile des Tagger-Blocks, der am Ende der Seite angezeigt wird.'; 98 | $_lang['setting_tagger.place_in_tab_label'] = 'Bezeichnung im Reiter'; 99 | $_lang['setting_tagger.place_in_tab_label_desc'] = 'Bezeichnung des Tagger-Blocks, der im Reiter angezeigt wird.'; 100 | $_lang['setting_tagger.place_tvs_tab_label'] = 'Bezeichnung im TV Bereich'; 101 | $_lang['setting_tagger.place_tvs_tab_label_desc'] = 'Bezeichnung des Tagger-Blocks, der im TV-Bereich angezeigt wird.'; 102 | $_lang['setting_tagger.place_bottom_page_label'] = 'Bezeichnung am Ende der Seite'; 103 | $_lang['setting_tagger.place_bottom_page_label_desc'] = 'Bezeichnung des Tagger-Blocks, der am Ende der Seite angezeigt wird.'; 104 | $_lang['setting_tagger.remove_accents_tag'] = 'Akzente in Tags entfernen'; 105 | $_lang['setting_tagger.remove_accents_tag_desc'] = 'Entfernen von Akzentzeichen aus dem Tag-Alias.'; 106 | $_lang['setting_tagger.remove_accents_group'] = 'Akzente in Gruppen entfernen'; 107 | $_lang['setting_tagger.remove_accents_group_desc'] = 'Entfernen von Akzentzeichen aus dem Gruppen-Alias.'; 108 | $_lang['area_places'] = 'Orte'; 109 | $_lang['area_default'] = 'Standard'; 110 | $_lang['area_settings'] = 'Einstellungen'; 111 | $_lang['tagger.err.group_name_ns'] = 'Der Gruppenname ist nicht angegeben. Bitte geben Sie einen Gruppennamen ein.'; 112 | $_lang['tagger.err.group_name_ae'] = 'Gruppe mit diesem Namen existiert bereits. Bitte wählen Sie einen anderen Namen.'; 113 | $_lang['tagger.err.tag_name_ns'] = 'Tag-Name ist nicht angegeben. Bitte geben Sie den Tag-Namen ein.'; 114 | $_lang['tagger.err.tag_name_ae'] = 'Ein Tag mit diesem Namen existiert bereits in der angegebenen Gruppe. Bitte wählen Sie einen anderen Namen oder eine andere Gruppe.'; 115 | $_lang['tagger.err.tag_group_changed'] = 'Die Tag-Gruppe kann nicht geändert werden.'; 116 | $_lang['tagger.err.bad_sort_column'] = 'Sortieren Sie die Tabelle nach [[+Spalte]], um die Drag & Drop-Sortierung zu verwenden.'; 117 | $_lang['tagger.err.clear_filter'] = 'Deaktivieren Sie die Suche, um die Drag & Drop-Sortierung zu verwenden.'; 118 | $_lang['tagger.err.import_group_ns'] = 'Die Gruppe ist nicht angegeben. Bitte wählen Sie eine Gruppe.'; 119 | $_lang['tagger.err.import_tv_ns'] = 'Die Template Variable ist nicht angegeben. Bitte wählen Sie eine TV.'; 120 | $_lang['tagger.err.import_tv_ne'] = 'Die Template Variable wurde nicht gefunden. Bitte wiederholen Sie Ihre Aktion.'; 121 | $_lang['tagger.err.import_tv_nsp'] = 'Der Typ der ausgewählten Template Variable wird nicht unterstützt. Unterstützte Typen sind: [[+supported]]'; 122 | $_lang['tagger.err.tag_assigned_resources_tag_ns'] = 'Der Tag ist nicht angegeben. Bitte wiederholen Sie Ihre Aktion.'; 123 | $_lang['tagger.err.tags_ns'] = 'Die Tags wurden nicht angegeben.'; 124 | $_lang['tagger.err.merge_same_groups'] = 'Sie können diese Tags nicht zusammenführen'; 125 | $_lang['tagger.err.merge_same_groups_desc'] = 'Ausgewählte Tags können nicht zusammengeführt werden, da sie zu verschiedenen Gruppen gehören.'; 126 | $_lang['tagger.err.tag_alias_ae'] = 'Ein Tag mit diesem Alias existiert bereits in der angegebenen Gruppe. Bitte wählen Sie einen anderen Alias oder eine andere Gruppe.'; 127 | $_lang['tagger.err.group_alias_ae'] = 'Eine Gruppe mit diesem Alias existiert bereits. Bitte wählen Sie einen anderen Alias.'; 128 | --------------------------------------------------------------------------------