diff --git a/INSTALL/MYSQL.sql b/INSTALL/MYSQL.sql index 0c26a8045..86f42f950 100644 --- a/INSTALL/MYSQL.sql +++ b/INSTALL/MYSQL.sql @@ -1358,7 +1358,7 @@ INSERT INTO `admin_settings` (`id`, `setting`, `value`) VALUES INSERT INTO `feeds` (`id`, `provider`, `name`, `url`, `distribution`, `default`, `enabled`) VALUES (1, 'CIRCL', 'CIRCL OSINT Feed', 'https://www.circl.lu/doc/misp/feed-osint', 3, 1, 0), -(2, 'Botvrij.eu', 'The Botvrij.eu Data', 'http://www.botvrij.eu/data/feed-osint', 3, 1, 0); +(2, 'Botvrij.eu', 'The Botvrij.eu Data', 'https://www.botvrij.eu/data/feed-osint', 3, 1, 0); INSERT INTO `regexp` (`id`, `regexp`, `replacement`, `type`) VALUES (1, '/.:.ProgramData./i', '%ALLUSERSPROFILE%\\\\', 'ALL'), diff --git a/INSTALL/POSTGRESQL-data-initial.sql b/INSTALL/POSTGRESQL-data-initial.sql index ebb35f845..ca95d6d33 100644 --- a/INSTALL/POSTGRESQL-data-initial.sql +++ b/INSTALL/POSTGRESQL-data-initial.sql @@ -118,7 +118,7 @@ COPY public.favourite_tags (id, tag_id, user_id) FROM stdin; COPY public.feeds (id, name, provider, url, rules, enabled, distribution, sharing_group_id, tag_id, "default", source_format, fixed_event, delta_merge, event_id, publish, override_ids, settings, input_source, delete_local_file, lookup_visible, headers, caching_enabled) FROM stdin; 1 CIRCL OSINT Feed CIRCL https://www.circl.lu/doc/misp/feed-osint \N f 3 0 0 t misp f f 0 f f \N network f f \N f -2 The Botvrij.eu Data Botvrij.eu http://www.botvrij.eu/data/feed-osint \N f 3 0 0 t misp f f 0 f f \N network f f \N f +2 The Botvrij.eu Data Botvrij.eu https://www.botvrij.eu/data/feed-osint \N f 3 0 0 t misp f f 0 f f \N network f f \N f \. diff --git a/INSTALL/misplogrotate.te b/INSTALL/misplogrotate.te index 4e3f35a7b..921989772 100644 --- a/INSTALL/misplogrotate.te +++ b/INSTALL/misplogrotate.te @@ -1,8 +1,9 @@ -module misplogrotate 1.1; +module misplogrotate 1.2; require { type httpd_t; type logrotate_t; type httpd_log_t; + type httpd_sys_script_t; type httpd_sys_content_t; type httpd_sys_rw_content_t; class dir { ioctl read getattr lock search open remove_name }; @@ -12,4 +13,4 @@ require { allow logrotate_t httpd_sys_content_t:dir { ioctl read getattr lock search open }; allow logrotate_t httpd_sys_rw_content_t:dir { ioctl read getattr lock search open }; allow httpd_t httpd_log_t:dir remove_name; -allow httpd_t httpd_log_t:file { unlink write }; +allow { httpd_t httpd_sys_script_t } httpd_log_t:file { unlink write }; diff --git a/PyMISP b/PyMISP index 21c16c8c7..204fd6ba8 160000 --- a/PyMISP +++ b/PyMISP @@ -1 +1 @@ -Subproject commit 21c16c8c75c61a00a0f01d1848687f7fe6455325 +Subproject commit 204fd6ba8cc916844156c1819c8375f6bbbca995 diff --git a/app/Config/config.default.php b/app/Config/config.default.php index 9f190e3f5..dc55fb86e 100644 --- a/app/Config/config.default.php +++ b/app/Config/config.default.php @@ -17,7 +17,7 @@ $config = array( 'org' => 'ORGNAME', 'showorg' => true, 'threatlevel_in_email_subject' => true, - 'email_subject_TLP_string' => 'TLP Amber', + 'email_subject_TLP_string' => 'tlp:amber', 'email_subject_tag' => 'tlp', 'email_subject_include_tag_name' => true, 'background_jobs' => true, @@ -141,7 +141,7 @@ $config = array( /* 'ApacheSecureAuth' => // Configuration for kerberos authentication array( - 'apacheEnv' => 'REMOTE_USER', // If proxy variable = HTTP_REMOTE_USER + 'apacheEnv' => 'REMOTE_USER', // If proxy variable = HTTP_REMOTE_USER, If BasicAuth ldap = PHP_AUTH_USER 'ldapServer' => 'ldap://example.com', // FQDN or IP 'ldapProtocol' => 3, 'ldapNetworkTimeout' => -1, // use -1 for unlimited network timeout diff --git a/app/Console/Command/ServerShell.php b/app/Console/Command/ServerShell.php index 215404032..52da69563 100644 --- a/app/Console/Command/ServerShell.php +++ b/app/Console/Command/ServerShell.php @@ -6,6 +6,32 @@ class ServerShell extends AppShell { public $uses = array('Server', 'Task', 'Job', 'User', 'Feed'); + public function list() { + $res = ['servers'=>[]]; + + $servers = $this->Server->find('all', [ + 'fields' => ['Server.id', 'Server.name', 'Server.url'], + 'recursive' => 0 + ]); + foreach ($servers as $server) + $res['servers'][] = $server['Server']; + + echo json_encode($res) . PHP_EOL; + } + + public function test() { + if (empty($this->args[0])) { + die('Usage: ' . $this->Server->command_line_functions['console_automation_tasks']['data']['Test'] . PHP_EOL); + } + + $serverId = intval($this->args[0]); + $res = @$this->Server->runConnectionTest($serverId); + if (!empty($res['message'])) + $res['message'] = json_decode($res['message']); + + echo json_encode($res) . PHP_EOL; + } + public function pull() { if (empty($this->args[0]) || empty($this->args[1])) { die('Usage: ' . $this->Server->command_line_functions['console_automation_tasks']['data']['pull'] . PHP_EOL); diff --git a/app/Controller/AttributesController.php b/app/Controller/AttributesController.php index 114908215..91ecf4da3 100644 --- a/app/Controller/AttributesController.php +++ b/app/Controller/AttributesController.php @@ -2959,6 +2959,7 @@ class AttributesController extends AppController public function addTag($id = false, $tag_id = false) { + $this->Taxonomy = $log = ClassRegistry::init('Taxonomy'); $rearrangeRules = array( 'request' => false, 'Attribute' => false, @@ -3099,6 +3100,20 @@ class AttributesController extends AppController $fails++; continue; } + $tagsOnAttribute = $this->Attribute->AttributeTag->find('all', array( + 'conditions' => array( + 'AttributeTag.attribute_id' => $id, + 'AttributeTag.local' => $local + ), + 'contain' => 'Tag', + 'fields' => array('Tag.name'), + 'recursive' => -1 + )); + $exclusiveTestPassed = $this->Taxonomy->checkIfNewTagIsAllowedByTaxonomy($tag['Tag']['name'], Hash::extract($tagsOnAttribute, '{n}.Tag.name')); + if (!$exclusiveTestPassed) { + $fails++; + continue; + } $this->Attribute->AttributeTag->create(); if ($this->Attribute->AttributeTag->save(array('attribute_id' => $id, 'tag_id' => $tag_id, 'event_id' => $eventId, 'local' => $local))) { if (!$local) { diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 511268933..e58853906 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -1153,6 +1153,7 @@ class EventsController extends AppController // remove galaxies tags $this->loadModel('GalaxyCluster'); + $this->loadModel('Taxonomy'); $cluster_names = $this->GalaxyCluster->find('list', array('fields' => array('GalaxyCluster.tag_name'), 'group' => array('GalaxyCluster.tag_name', 'GalaxyCluster.id'))); foreach ($event['Object'] as $k => $object) { if (isset($object['Attribute'])) { @@ -1162,6 +1163,14 @@ class EventsController extends AppController unset($event['Object'][$k]['Attribute'][$k2]['AttributeTag'][$k3]); } } + $tagConflicts = $this->Taxonomy->checkIfTagInconsistencies($attribute['AttributeTag']); + foreach ($tagConflicts['global'] as $tagConflict) { + $warningTagConflicts[$tagConflict['taxonomy']['Taxonomy']['namespace']] = $tagConflict['taxonomy']; + } + foreach ($tagConflicts['local'] as $tagConflict) { + $warningTagConflicts[$tagConflict['taxonomy']['Taxonomy']['namespace']] = $tagConflict['taxonomy']; + } + $event['Object'][$k]['Attribute'][$k2]['tagConflicts'] = $tagConflicts; } } } @@ -1171,6 +1180,14 @@ class EventsController extends AppController unset($event['Attribute'][$k]['AttributeTag'][$k2]); } } + $tagConflicts = $this->Taxonomy->checkIfTagInconsistencies($attribute['AttributeTag']); + foreach ($tagConflicts['global'] as $tagConflict) { + $warningTagConflicts[$tagConflict['taxonomy']['Taxonomy']['namespace']] = $tagConflict['taxonomy']; + } + foreach ($tagConflicts['local'] as $tagConflict) { + $warningTagConflicts[$tagConflict['taxonomy']['Taxonomy']['namespace']] = $tagConflict['taxonomy']; + } + $event['Attribute'][$k]['tagConflicts'] = $tagConflicts; } if (empty($this->passedArgs['sort'])) { $filters['sort'] = 'timestamp'; @@ -1269,12 +1286,14 @@ class EventsController extends AppController private function __viewUI($event, $continue, $fromEvent) { + $this->loadModel('Taxonomy'); $filterData = array( 'request' => $this->request, 'paramArray' => $this->acceptedFilteringNamedParams, 'named_params' => $this->params['named'] ); $exception = false; + $warningTagConflicts = array(); $filters = $this->_harvestParameters($filterData, $exception); $this->loadModel('GalaxyCluster'); @@ -1375,6 +1394,16 @@ class EventsController extends AppController unset($event['EventTag'][$k]); } } + + $tagConflicts = $this->Taxonomy->checkIfTagInconsistencies($event['EventTag']); + foreach ($tagConflicts['global'] as $tagConflict) { + $warningTagConflicts[$tagConflict['taxonomy']['Taxonomy']['namespace']] = $tagConflict['taxonomy']; + } + foreach ($tagConflicts['local'] as $tagConflict) { + $warningTagConflicts[$tagConflict['taxonomy']['Taxonomy']['namespace']] = $tagConflict['taxonomy']; + } + $this->set('tagConflicts', $tagConflicts); + $startDate = null; $modificationMap = array(); foreach ($event['Attribute'] as $k => $attribute) { @@ -1391,6 +1420,14 @@ class EventsController extends AppController unset($event['Attribute'][$k]['AttributeTag'][$k2]); } } + $tagConflicts = $this->Taxonomy->checkIfTagInconsistencies($attribute['AttributeTag']); + foreach ($tagConflicts['global'] as $tagConflict) { + $warningTagConflicts[$tagConflict['taxonomy']['Taxonomy']['namespace']] = $tagConflict['taxonomy']; + } + foreach ($tagConflicts['local'] as $tagConflict) { + $warningTagConflicts[$tagConflict['taxonomy']['Taxonomy']['namespace']] = $tagConflict['taxonomy']; + } + $event['Attribute'][$k]['tagConflicts'] = $tagConflicts; } $attributeTagsName = $this->Event->Attribute->AttributeTag->extractAttributeTagsNameFromEvent($event, 'both'); $this->set('attributeTags', array_values($attributeTagsName['tags'])); @@ -1416,9 +1453,18 @@ class EventsController extends AppController unset($event['Object'][$k]['Attribute'][$k2]['AttributeTag'][$k3]); } } + $tagConflicts = $this->Taxonomy->checkIfTagInconsistencies($attribute['AttributeTag']); + foreach ($tagConflicts['global'] as $tagConflict) { + $warningTagConflicts[$tagConflict['taxonomy']['Taxonomy']['namespace']] = $tagConflict['taxonomy']; + } + foreach ($tagConflicts['local'] as $tagConflict) { + $warningTagConflicts[$tagConflict['taxonomy']['Taxonomy']['namespace']] = $tagConflict['taxonomy']; + } + $event['Object'][$k]['Attribute'][$k2]['tagConflicts'] = $tagConflicts; } } } + $this->set('warningTagConflicts', $warningTagConflicts); $filters['sort'] = 'timestamp'; $filters['direction'] = 'desc'; if (isset($filters['distribution'])) { @@ -3734,6 +3780,7 @@ class EventsController extends AppController public function addTag($id = false, $tag_id = false) { + $this->loadModel('Taxonomy'); $rearrangeRules = array( 'request' => false, 'Event' => false, @@ -3848,6 +3895,20 @@ class EventsController extends AppController $error = __('Tag is already attached to this event.'); continue; } + $tagsOnEvent = $this->Event->EventTag->find('all', array( + 'conditions' => array( + 'EventTag.event_id' => $id, + 'EventTag.local' => $local + ), + 'contain' => 'Tag', + 'fields' => array('Tag.name'), + 'recursive' => -1 + )); + $exclusiveTestPassed = $this->Taxonomy->checkIfNewTagIsAllowedByTaxonomy($tag['Tag']['name'], Hash::extract($tagsOnEvent, '{n}.Tag.name')); + if (!$exclusiveTestPassed) { + $fail = __('Tag is not allowed due to taxonomy exclusivity settings'); + continue; + } $this->Event->EventTag->create(); if ($this->Event->EventTag->save(array('event_id' => $id, 'tag_id' => $tag_id, 'local' => $local))) { if (!$local) { diff --git a/app/Controller/TagsController.php b/app/Controller/TagsController.php index e75f36d0c..c5c1e9778 100644 --- a/app/Controller/TagsController.php +++ b/app/Controller/TagsController.php @@ -459,6 +459,7 @@ class TagsController extends AppController public function showEventTag($id) { $this->loadModel('EventTag'); + $this->loadModel('Taxonomy'); if (!$this->EventTag->Event->checkIfAuthorised($this->Auth->user(), $id)) { throw new MethodNotAllowedException('Invalid event.'); } @@ -487,6 +488,8 @@ class TagsController extends AppController 'conditions' => array('Event.id' => $id) )); $this->set('required_taxonomies', $this->EventTag->Event->getRequiredTaxonomies()); + $tagConflicts = $this->Taxonomy->checkIfTagInconsistencies($tags); + $this->set('tagConflicts', $tagConflicts); $this->set('event', $event); $this->layout = 'ajax'; $this->render('/Events/ajax/ajaxTags'); @@ -496,6 +499,7 @@ class TagsController extends AppController { $this->helpers[] = 'TextColour'; $this->loadModel('AttributeTag'); + $this->loadModel('Taxonomy'); $this->Tag->AttributeTag->Attribute->id = $id; if (!$this->Tag->AttributeTag->Attribute->exists()) { @@ -528,6 +532,8 @@ class TagsController extends AppController $this->set('event', $event); $this->set('attributeTags', $attributeTags); $this->set('attributeId', $id); + $tagConflicts = $this->Taxonomy->checkIfTagInconsistencies($attributeTags); + $this->set('tagConflicts', $tagConflicts); $this->layout = 'ajax'; $this->render('/Attributes/ajax/ajaxAttributeTags'); } diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php index 13c660694..55da06a37 100644 --- a/app/Model/AppModel.php +++ b/app/Model/AppModel.php @@ -122,7 +122,7 @@ class AppModel extends Model break; case '2.4.27': $newFeeds = array( - array('provider' => 'Botvrij.eu', 'name' => 'The Botvrij.eu Data','url' => 'http://www.botvrij.eu/data/feed-osint', 'enabled' => 0) + array('provider' => 'Botvrij.eu', 'name' => 'The Botvrij.eu Data','url' => 'https://www.botvrij.eu/data/feed-osint', 'enabled' => 0) ); $this->__addNewFeeds($newFeeds); break; diff --git a/app/Model/Event.php b/app/Model/Event.php index 39d630bbe..0e180a477 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -3178,7 +3178,7 @@ class Event extends AppModel $bodyevent = $temp[0]; $body = $temp[1]; $result = true; - $tplColorString = !empty(Configure::read('MISP.email_subject_TLP_string')) ? Configure::read('MISP.email_subject_TLP_string') : "TLP Amber"; + $tplColorString = !empty(Configure::read('MISP.email_subject_TLP_string')) ? Configure::read('MISP.email_subject_TLP_string') : "tlp:amber"; $subject = "[" . Configure::read('MISP.org') . " MISP] Need info about event " . $id . " - ".$tplColorString; $result = $this->User->sendEmail($reporter, $bodyevent, $body, $subject, $user) && $result; } diff --git a/app/Model/Post.php b/app/Model/Post.php index eac2a6b14..199450eec 100644 --- a/app/Model/Post.php +++ b/app/Model/Post.php @@ -118,7 +118,7 @@ class Post extends AppModel $bodyDetail .= "The following message was added: \n"; $bodyDetail .= "\n"; $bodyDetail .= $message . "\n"; - $tplColorString = !empty(Configure::read('MISP.email_subject_TLP_string')) ? Configure::read('MISP.email_subject_TLP_string') : "TLP Amber"; + $tplColorString = !empty(Configure::read('MISP.email_subject_TLP_string')) ? Configure::read('MISP.email_subject_TLP_string') : "tlp:amber"; $subject = "[" . Configure::read('MISP.org') . " MISP] New post in discussion " . $post['Post']['thread_id'] . " - ".$tplColorString; foreach ($orgMembers as $recipient) { $this->User->sendEmail($recipient, $bodyDetail, $body, $subject); diff --git a/app/Model/Server.php b/app/Model/Server.php index d792259c4..654e8529d 100644 --- a/app/Model/Server.php +++ b/app/Model/Server.php @@ -134,7 +134,9 @@ class Server extends AppModel 'Push' => 'MISP/app/Console/cake Server push [user_id] [server_id]', 'Cache feeds for quick lookups' => 'MISP/app/Console/cake Server cacheFeed [user_id] [feed_id|all|csv|text|misp]', 'Fetch feeds as local data' => 'MISP/app/Console/cake Server fetchFeed [user_id] [feed_id|all|csv|text|misp]', - 'Run enrichment' => 'MISP/app/Console/cake Event enrichEvent [user_id] [event_id] [json_encoded_module_list]' + 'Run enrichment' => 'MISP/app/Console/cake Event enrichEvent [user_id] [event_id] [json_encoded_module_list]', + 'Test' => 'MISP/app/Console/cake Server test [server_id]', + 'List' => 'MISP/app/Console/cake Server list' ), 'description' => __('If you would like to automate tasks such as caching feeds or pulling from server instances, you can do it using the following command line tools. Simply execute the given commands via the command line / create cron jobs easily out of them.'), 'header' => __('Automating certain console tasks') @@ -428,7 +430,7 @@ class Server extends AppModel 'email_subject_TLP_string' => array( 'level' => 2, 'description' => __('This is the TLP string for e-mails when email_subject_tag is not found.'), - 'value' => 'TLP Amber', + 'value' => 'tlp:amber', 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', @@ -4344,7 +4346,7 @@ class Server extends AppModel public function stixDiagnostics(&$diagnostic_errors, &$stixVersion, &$cyboxVersion, &$mixboxVersion, &$maecVersion, &$stix2Version, &$pymispVersion) { $result = array(); - $expected = array('stix' => '>1.2.0.6', 'cybox' => '>2.1.0.18.dev0', 'mixbox' => '1.0.3', 'maec' => '>4.1.0.14', 'stix2' => '1.2.0', 'pymisp' => '>2.4.93'); + $expected = array('stix' => '>1.2.0.6', 'cybox' => '>2.1.0.18.dev0', 'mixbox' => '1.0.3', 'maec' => '>4.1.0.14', 'stix2' => '>1.2.0', 'pymisp' => '>2.4.93'); // check if the STIX and Cybox libraries are working using the test script stixtest.py $scriptResult = shell_exec($this->getPythonVersion() . ' ' . APP . 'files' . DS . 'scripts' . DS . 'stixtest.py'); $scriptResult = json_decode($scriptResult, true); diff --git a/app/Model/Taxonomy.php b/app/Model/Taxonomy.php index 5bc21a528..c987b176a 100644 --- a/app/Model/Taxonomy.php +++ b/app/Model/Taxonomy.php @@ -101,7 +101,7 @@ class Taxonomy extends AppModel } $this->deleteAll(array('Taxonomy.namespace' => $current['Taxonomy']['namespace'])); } - $taxonomy['Taxonomy'] = array('namespace' => $vocab['namespace'], 'description' => $vocab['description'], 'version' => $vocab['version'], 'enabled' => $enabled); + $taxonomy['Taxonomy'] = array('namespace' => $vocab['namespace'], 'description' => $vocab['description'], 'version' => $vocab['version'], 'exclusive' => $vocab['exclusive'], 'enabled' => $enabled); $predicateLookup = array(); foreach ($vocab['predicates'] as $k => $predicate) { $taxonomy['Taxonomy']['TaxonomyPredicate'][$k] = $predicate; @@ -489,27 +489,29 @@ class Taxonomy extends AppModel return $taxonomies; } - public function getTaxonomyForTag($tagName, $metaOnly = false) + public function getTaxonomyForTag($tagName, $metaOnly = false, $fullTaxonomy = False) { if (preg_match('/^[^:="]+:[^:="]+="[^:="]+"$/i', $tagName)) { $temp = explode(':', $tagName); $pieces = array_merge(array($temp[0]), explode('=', $temp[1])); $pieces[2] = trim($pieces[2], '"'); + $contain = array( + 'TaxonomyPredicate' => array( + 'TaxonomyEntry' => array() + ) + ); + if (!$fullTaxonomy) { + $contain['TaxonomyPredicate']['conditions'] = array( + 'LOWER(TaxonomyPredicate.value)' => strtolower($pieces[1]) + ); + $contain['TaxonomyPredicate']['TaxonomyEntry']['conditions'] = array( + 'LOWER(TaxonomyEntry.value)' => strtolower($pieces[2]) + ); + } $taxonomy = $this->find('first', array( 'recursive' => -1, 'conditions' => array('LOWER(Taxonomy.namespace)' => strtolower($pieces[0])), - 'contain' => array( - 'TaxonomyPredicate' => array( - 'conditions' => array( - 'LOWER(TaxonomyPredicate.value)' => strtolower($pieces[1]) - ), - 'TaxonomyEntry' => array( - 'conditions' => array( - 'LOWER(TaxonomyEntry.value)' => strtolower($pieces[2]) - ) - ) - ) - ) + 'contain' => $contain )); if ($metaOnly && !empty($taxonomy)) { return array('Taxonomy' => $taxonomy['Taxonomy']); @@ -517,16 +519,16 @@ class Taxonomy extends AppModel return $taxonomy; } elseif (preg_match('/^[^:="]+:[^:="]+$/i', $tagName)) { $pieces = explode(':', $tagName); + $contain = array('TaxonomyPredicate' => array()); + if (!$fullTaxonomy) { + $contain['TaxonomyPredicate']['conditions'] = array( + 'LOWER(TaxonomyPredicate.value)' => strtolower($pieces[1]) + ); + } $taxonomy = $this->find('first', array( 'recursive' => -1, 'conditions' => array('LOWER(Taxonomy.namespace)' => strtolower($pieces[0])), - 'contain' => array( - 'TaxonomyPredicate' => array( - 'conditions' => array( - 'LOWER(TaxonomyPredicate.value)' => strtolower($pieces[1]) - ) - ) - ) + 'contain' => $contain )); if ($metaOnly && !empty($taxonomy)) { return array('Taxonomy' => $taxonomy['Taxonomy']); @@ -536,4 +538,97 @@ class Taxonomy extends AppModel return false; } } + + // Remove the value for triple component tags or the predicate for double components tags + public function stripLastTagComponent($tagName) + { + $shortenedTag = ''; + if (preg_match('/^[^:="]+:[^:="]+="[^:="]+"$/i', $tagName)) { + $shortenedTag = explode('=', $tagName)[0]; + } elseif (preg_match('/^[^:="]+:[^:="]+$/i', $tagName)) { + $shortenedTag = explode(':', $tagName)[0]; + } + return $shortenedTag; + } + + public function checkIfNewTagIsAllowedByTaxonomy($newTagName, $tagNameList=array()) + { + $newTagShortened = $this->stripLastTagComponent($newTagName); + $prefixIsFree = true; + foreach ($tagNameList as $tagName) { + $tagShortened = $this->stripLastTagComponent($tagName); + if ($newTagShortened == $tagShortened) { + $prefixIsFree = false; + } + } + if (!$prefixIsFree) { + // at this point, we have a duplicated namespace(-predicate) + $taxonomy = $this->getTaxonomyForTag($newTagName); + if (!empty($taxonomy['Taxonomy']['exclusive'])) { + return false; // only one tag of this taxonomy is allowed + } elseif (!empty($taxonomy['TaxonomyPredicate'][0]['exclusive'])) { + return false; // only one tag belonging to this predicate is allowed + } + } + return true; + } + + public function checkIfTagInconsistencies($tagList) + { + $eventTags = array(); + $localEventTags = array(); + foreach($tagList as $tag) { + if ($tag['local'] == 0) { + $eventTags[] = $tag['Tag']['name']; + } else { + $localEventTags[] = $tag['Tag']['name']; + } + } + $tagConflicts = $this->getTagConflicts($eventTags); + $localTagConflicts = $this->getTagConflicts($localEventTags); + return array( + 'global' => $tagConflicts, + 'local' => $localTagConflicts + ); + } + + public function getTagConflicts($tagNameList) + { + $potentiallyConflictingTaxonomy = array(); + $conflictingTaxonomy = array(); + foreach ($tagNameList as $tagName) { + $tagShortened = $this->stripLastTagComponent($tagName); + if (isset($potentiallyConflictingTaxonomy[$tagShortened])) { + $potentiallyConflictingTaxonomy[$tagShortened]['taxonomy'] = $this->getTaxonomyForTag($tagName); + $potentiallyConflictingTaxonomy[$tagShortened]['count']++; + } else { + $potentiallyConflictingTaxonomy[$tagShortened] = array( + 'count' => 1 + ); + } + $potentiallyConflictingTaxonomy[$tagShortened]['tagNames'][] = $tagName; + } + foreach ($potentiallyConflictingTaxonomy as $potTaxonomy) { + if ($potTaxonomy['count'] > 1) { + $taxonomy = $potTaxonomy['taxonomy']; + if (isset($taxonomy['Taxonomy']['exclusive']) && $taxonomy['Taxonomy']['exclusive']) { + $conflictingTaxonomy[] = array( + 'tags' => $potTaxonomy['tagNames'], + 'taxonomy' => $taxonomy, + 'conflict' => sprintf(__('Taxonomy `%s` is an exclusive Taxonomy'), $taxonomy['Taxonomy']['namespace']) + ); + } elseif (isset($taxonomy['TaxonomyPredicate'][0]['exclusive']) && $taxonomy['TaxonomyPredicate'][0]['exclusive']) { + $conflictingTaxonomy[] = array( + 'tags' => $potTaxonomy['tagNames'], + 'taxonomy' => $taxonomy, + 'conflict' => sprintf( + __('Predicate `%s` is exclusive'), + $taxonomy['TaxonomyPredicate'][0]['value'] + ) + ); + } + } + } + return $conflictingTaxonomy; + } } diff --git a/app/View/Elements/Events/View/row_attribute.ctp b/app/View/Elements/Events/View/row_attribute.ctp index 7fcb78487..a252b713b 100644 --- a/app/View/Elements/Events/View/row_attribute.ctp +++ b/app/View/Elements/Events/View/row_attribute.ctp @@ -156,7 +156,7 @@
- element('ajaxTags', array('attributeId' => $object['id'], 'tags' => $object['AttributeTag'], 'tagAccess' => ($isSiteAdmin || $mayModify || $me['org_id'] == $event['Event']['org_id']), 'context' => $context, 'scope' => 'attribute')); ?> + element('ajaxTags', array('attributeId' => $object['id'], 'tags' => $object['AttributeTag'], 'tagAccess' => ($isSiteAdmin || $mayModify || $me['org_id'] == $event['Event']['org_id']), 'context' => $context, 'scope' => 'attribute', 'tagConflicts' => isset($object['tagConflicts']) ? $object['tagConflicts'] : array())); ?>
'attributes', 'action' => 'download', $object['id']); + $controller = isset($object['objectType']) && $object['objectType'] === 'proposal' ? 'shadow_attributes' : 'attributes'; + $url = array('controller' => $controller, 'action' => 'download', $object['id']); echo $this->Html->link($filename, $url, array('class' => $linkClass)); if (isset($filenameHash[1])) { echo '
' . $filenameHash[1]; diff --git a/app/View/Elements/ajaxTags.ctp b/app/View/Elements/ajaxTags.ctp index 3a4174c43..6a0c15255 100644 --- a/app/View/Elements/ajaxTags.ctp +++ b/app/View/Elements/ajaxTags.ctp @@ -162,4 +162,36 @@ '%s', $tagData ); + $tagConflictData = ''; + if (!empty($tagConflicts['global'])) { + $tagConflictData .= '
'; + $tagConflictData .= ''; + $tagConflictData .= '
'; + foreach ($tagConflicts['global'] as $tagConflict) { + $tagConflictData .= sprintf( + '%s
', + h($tagConflict['conflict']) + ); + foreach ($tagConflict['tags'] as $tag) { + $tagConflictData .= sprintf('%s
', h($tag)); + } + } + $tagConflictData .= '
'; + } + if (!empty($tagConflicts['local'])) { + $tagConflictData .= '
'; + $tagConflictData .= ''; + $tagConflictData .= '
'; + foreach ($tagConflicts['local'] as $tagConflict) { + $tagConflictData .= sprintf( + '%s
', + h($tagConflict['conflict']) + ); + foreach ($tagConflict['tags'] as $tag) { + $tagConflictData .= sprintf('%s
', h($tag)); + } + } + $tagConflictData .= '
'; + } + echo $tagConflictData; ?> diff --git a/app/View/Events/ajax/ajaxTags.ctp b/app/View/Events/ajax/ajaxTags.ctp index 6001997d0..e56be1370 100644 --- a/app/View/Events/ajax/ajaxTags.ctp +++ b/app/View/Events/ajax/ajaxTags.ctp @@ -6,6 +6,7 @@ echo $this->element('ajaxTags', array( 'event' => $event, 'tags' => $tags, - 'tagAccess' => ($isSiteAdmin || $mayModify) + 'tagAccess' => ($isSiteAdmin || $mayModify), + 'tagConflicts' => $tagConflicts )); ?> diff --git a/app/View/Events/view.ctp b/app/View/Events/view.ctp index d3c4615f7..fbd8ecb9d 100644 --- a/app/View/Events/view.ctp +++ b/app/View/Events/view.ctp @@ -133,7 +133,8 @@ 'event' => $event, 'tags' => $event['EventTag'], 'tagAccess' => ($isSiteAdmin || $mayModify || $me['org_id'] == $event['Event']['orgc_id']), - 'required_taxonomies' => $required_taxonomies + 'required_taxonomies' => $required_taxonomies, + 'tagConflicts' => $tagConflicts ) ) ) @@ -330,6 +331,41 @@ element('genericElements/viewMetaTable', array('table_data' => $table_data)); ?>