diff --git a/code/web/app/app_achievements_admin/class/ADM_inter.php b/code/web/app/app_achievements_admin/class/ADM_inter.php new file mode 100644 index 000000000..e158827c7 --- /dev/null +++ b/code/web/app/app_achievements_admin/class/ADM_inter.php @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/AdmAchievement_class.php b/code/web/app/app_achievements_admin/class/AdmAchievement_class.php new file mode 100644 index 000000000..ea76b144e --- /dev/null +++ b/code/web/app/app_achievements_admin/class/AdmAchievement_class.php @@ -0,0 +1,129 @@ +insert(); + $this->nodes[] = $n; + } + + function removeNode($id) { // remove a Perk + $res = $this->getNode($id); + if($res != null) { + $res->delete_me(); + $this->unsetChild($id); + } + } + + function updateNode($id,$data) { // update a Perk + $res = $this->getNode($id); + if($res != null) { + #MISSING: set new data + # + $res->update(); + } + } + + function getNode($id) { // find a Perk + foreach($this->nodes as $elem) { + if($elem->getID == $id) { + return $elem; + } + } + + return null; + } + + function delete_me() { + global $DBc; + + $DBc->sqlQuery("DELETE FROM ach_achievement WHERE aa_id='".$this->getID()."'"); + $DBc->sqlQuery("DELETE FROM ach_player_achievement WHERE apa_id='".$this->getID()."'"); + $DBc->sqlQuery("DELETE FROM ach_achievement_lang WHERE NOT EXISTS (SELECT * FROM ach_achievement WHERE aa_id=aal_achievement)"); + + foreach($this->nodes as $elem) { + $elem->delete_me(); + $this->unsetChild($elem->getID()); + } + } + + function update() { + global $DBc; + + $DBc->sqlQuery("UPDATE ach_achievement SET aa_parent='".$this->getParent())."',aa_tie_race='".mysql_real_escape_string($this->getTieRace())."',aa_tie_cult='".mysql_real_escape_string($this->getTieCult())."',aa_tie_civ='".mysql_real_escape_string($this->getTieCiv())."',aa_image='".mysql_real_escape_string($this->getImage())."',aa_dev='".$this->getDev()."' WHERE aa_id='".$this->geID()."'"); + + #MISSING: update lang entry + } + + function insert() { + + } + + function unsetChild($id) { // remove child with given ID from nodes list; unset should destruct it. + foreach($this->nodes as $key=>$elem) { + if($elem->getID() == $id) { + unset($this->nodes[$key]); + return null; + } + } + } + + function setInDev($tf) { + if($tf == true) { + $this->setDev(1); + } + else { + $this->setDev(0); + } + + $this->update(); + } + + function setDev($d) { + $this->dev = $d; + } + + function setID($id) { + $this->id = $id; + } + + function setParent($p) { + $this->parent = $p + } + + function setCategory($c) { + $this->category = $c; + } + + function setTieRace($t) { + $this->tie_race = $t; + } + + function setTieCiv($t) { + $this->tie_civ = $t; + } + + function setTieCult($t) { + $this->tie_cult = $t; + } + + function setImage($i) { + $this->image = $i; + } + + function setName($n) { + $this->name = $n; + } + + function setTemplate($i) { + $this->template = $t; + } + } +?> \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/AdmAtom_class.php b/code/web/app/app_achievements_admin/class/AdmAtom_class.php new file mode 100644 index 000000000..17bde5bc9 --- /dev/null +++ b/code/web/app/app_achievements_admin/class/AdmAtom_class.php @@ -0,0 +1,67 @@ +parent = $parent; + $this->id = $data['atom_id']; + $this->objective = $data['atom_objective']; + $this->mandatory = $data['atom_mandatory']; + $this->ruleset = $data['atom_ruleset']; + $this->ruleset_parsed = $data['atom_ruleset_parsed']; + } + + function delete_me() { // aaaaand... it's gone ^^ + global $DBc; + + $DBc->sqlQuery("DELETE FROM ach_atom WHERE atom_id='".$this->id."'"); + $DBc->sqlQuery("DELETE FROM ach_player_atom WHERE apa_atom='".$this->id."'"); + } + + function update() { + $DBc->sqlQuery("UPDATE ach_atom SET atom_mandatory='".."',atom_ruleset='".."',atom_ruleset_parsed='".."' WHERE atom_id='".$this->id."'"); + } + + function insert() { + $DBc->sqlQuery("INSERT INTO ach_atom (atom_objective,atom_mandatory,atom_ruleset,atom_ruleset_parsed) VALUES ('".."','".."','".."','".."')"); + $id = mysql_insert_id(); + $this->setID($id); + } + + function setMandatory($ft) { + if($ft == true) { + $this->mandatory = 1; + } + else { + $this->mandatory = 0; + } + } + + function setRuleset($r) { + $this->ruleset = $r; + $this->parse(); + } + + function getMandatory() { + return $this->mandatory; + } + + function isMandatory() { + return ($this->mandatory == 1); + } + + function getRuleset() { + return $this->ruleset; + } + + private function parse() { + + } + } +?> \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/AdmCategory_class.php b/code/web/app/app_achievements_admin/class/AdmCategory_class.php new file mode 100644 index 000000000..1ae64f0e1 --- /dev/null +++ b/code/web/app/app_achievements_admin/class/AdmCategory_class.php @@ -0,0 +1,54 @@ +insert(); + $this->nodes[] = $n; + } + + function removeNode($id) { + $res = $this->getNode($id); + if($res != null) { + $res->delete_me(); + $this->unsetChild($id); + } + } + + function updateNode($id,$data) { + $res = $this->getNode($id); + if($res != null) { + #MISSING: set new data + #aa_id aa_category aa_parent aa_tie_race aa_tie_cult aa_tie_civ aa_image aa_dev + $res->update(); + } + } + + function getNode($id) { // try to find the Achievement node that has the given ID. Return null on failure. + foreach($this->nodes as $elem) { + if($elem->getID == $id) { + return $elem; + } + } + + return null; + } + + function unsetChild($id) { // remove child with given ID from nodes list; unset should destruct it. + foreach($this->nodes as $key=>$elem) { + if($elem->getID() == $id) { + unset($this->nodes[$key]); + return null; + } + } + } + + } +?> \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/AdmDispatcher_inter.php b/code/web/app/app_achievements_admin/class/AdmDispatcher_inter.php new file mode 100644 index 000000000..102b7c893 --- /dev/null +++ b/code/web/app/app_achievements_admin/class/AdmDispatcher_inter.php @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/AdmMenuNode_class.php b/code/web/app/app_achievements_admin/class/AdmMenuNode_class.php new file mode 100644 index 000000000..79f60c9df --- /dev/null +++ b/code/web/app/app_achievements_admin/class/AdmMenuNode_class.php @@ -0,0 +1,185 @@ +sqlQuery("SELECT count(*) as anz FROM ach_achievement WHERE aa_category='".$this->id."'"); + $this->ach_count = $res[0]['anz']; + } + + protected function makeChild($d) { // override child generator to use admin classes + return new AdmMenuNode($d,$this); + } + + function hasAchievements() { + if($this->ach_count != 0) { + return true; + } + else { + foreach($this->nodes as $elem) { + $res = $elem->hasAchievements(); + if($res == true) { + return true; + } + } + + return false; + } + } + + function getNode($id) { // try to find the child node that has the given ID. Return null on failure. + if($id == $this->getID()) { // found! + return $this; + } + else { + foreach($this->nodes as $elem) { // check children + $tmp = $elem->getNode($id); + if($tmp != null) { + return $tmp; + } + } + return null; + } + } + + function delete_me() { // remove this node + global $DBc; + + // remove from database + $DBc->sqlQuery("DELETE FROM ach_category WHERE ac_id='".$this->getID()."'"); + $DBc->sqlQuery("DELETE FROM ach_category WHERE ac_parent='".$this->getID()."'"); + $DBc->sqlQuery("DELETE FROM ach_category_lang WHERE NOT EXISTS (SELECT * FROM ach_category WHERE ac_id=acl_category)"); + + // call delete function for all children + foreach($this->nodes as $elem) { + $elem->delete_me(); + $this->unsetChild($elem->getID()); + } + } + + function unsetChild($id) { // remove child with given ID from nodes list; unset should destruct. + foreach($this->nodes as $key=>$elem) { + if($elem->getID() == $id) { + unset($this->nodes[$key]); + return null; + } + } + } + + function insertChild(&$n) { // insert a new child + // insert command to create database entry + $n->insert(); + + // set the new child's parent and add it to the node list + $n->setParent($this); + $this->nodes[] = $n; + } + + function update() { + global $DBc,$_USER; + + $DBc->sqlQuery("UPDATE ach_category SET ac_parent=".mkn($this->getParentID()).",ac_order='".$this->getOrder()."',ac_image=".mkn($this->getImage()).",ac_dev='".$this->getDev()."' WHERE ac_id='".$this->getID()."'"); + + #MISSING: update lang entry + $DBc->sqlQuery("INSERT IGNORE INTO ach_category_lang (acl_category,acl_lang,acl_name) VALUES ('".$this->getID()."','".$_USER->getLang()."','".mysql_real_escape_string($this->getName())."') ON DUPLICATE KEY UPDATE acl_name='".mysql_real_escape_string($this->getName())."'"); + } + + function insert() { // write $this to the database as a new entry + global $DBc,$_USER; + + $this->setOrder($this->parent->getNextOrder()); + + $DBc->sqlQuery("INSERT INTO ach_category (ac_parent,ac_order,ac_image,ac_dev) VALUES (".mkn($this->getParentID()).",'".$this->getOrder()."',".mkn($this->getImage()).",'1')"); + $id = mysql_insert_id(); + $this->setID($id); + #MISSING: insert lang entry + $DBc->sqlQuery("INSERT INTO ach_category_lang (acl_category,acl_lang,acl_name) VALUES ('".$this->getID()."','".$_USER->getLang()."','".mysql_real_escape_string($this->getName())."')"); + + } + + function setInDev($tf) { + if($tf == true) { + $this->setDev(1); + } + else { + $this->setDev(0); + } + + $this->update(); + } + + private function setDev($d) { + $this->dev = $d; + } + + private function setOrder($o) { + $this->order = $o; + $this->update(); + } + + function swapChild($a,$b) { + $ids = array(); + foreach($this->nodes as $key=>$elem) { + if($a == $elem->getID() || $b == $elem->getID()) { + $ids[] = $key; + } + + if(sizeof($ids) == 2) { + break; + } + } + + $tmp = $this->nodes[$ids[0]]; + $this->nodes[$ids[0]] = $this->nodes[$tmp[1]]; + $this->nodes[$ids[1]] = $tmp; + } + + function setName($n) { + $this->name = $n; + } + + function setImage($i) { + if($i == null || strtolower($i) == "null") { + $this->image = null; + } + else { + $this->image = $i; + } + } + + function setParent(&$p) { + $this->parent = $p; + } + + function setParentID($p) { + if($p == null || strtolower($p) == "null") { + $this->parent_id = null; + } + else { + $this->parent_id = $p; + } + } + + function setID($id) { + $this->id = $id; + } + + function getNextOrder() { + if($this->isEmpty()) { + return 0; + } + + $val = array(); + foreach($this->nodes as $elem) { + $val[] = $elem->getOrder(); + } + + return (max($val)+1); + } + } + +?> \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/AdmMenu_class.php b/code/web/app/app_achievements_admin/class/AdmMenu_class.php new file mode 100644 index 000000000..524f249d0 --- /dev/null +++ b/code/web/app/app_achievements_admin/class/AdmMenu_class.php @@ -0,0 +1,95 @@ +nodes[0]); // unset the auto-generated "summary" node + } + + protected function makeChild($d) { // override child generator to use admin classes + return new AdmMenuNode($d,$this); + } + + function removeNode($id) { // find the node that has the ID we want to delete. If found, call it's delete function. + $res = $this->getNode($id); + if($res != null) { + $res->delete_me(); + $this->unsetChild($id); + } + } + + function insertNode(&$n) { + if($n->getParentID() != null) { + $res = $this->getNode($n->getParentID()); + if($res != null) { + $n->setParent($res); + $res->insertChild($n); + } + } + else { + $n->setParent($this); + $n->insert(); + $this->nodes[] = $n; + } + } + + function updateNode($id,$data) { #MISSING: data handling... + $res = $this->getNode($id); + if($res != null) { + $res->setName($data['acl_name']); + $res->setImage($data['ac_image']); + $res->update(); + } + } + + function swapOrder($a,$b) { + $tmp_a = $this->getNode($a); + if($tmp_a != null) { + $tmp_b = $this->getNode($a); + if($tmp_b != null) { + $tmp = $tmp_b->getOrder(); + $tmp_b->setOrder($tmp_a->getOrder()); + $tmp_a->setOrder($tmp); + + if($tmp_a->getParentID() == $tmp_b->getParentID()) { + $tmp_a->getParent()->swapChild($a,$b); + } + } + } + } + + function getNode($id) { // try to find the MenuNode that has the given ID. Return null on failure. + foreach($this->nodes as $elem) { + $tmp = $elem->getNode($id); + if($tmp != null) { + return $tmp; + } + } + + return null; + } + + function getNextOrder() { + if($this->isEmpty()) { + return 0; + } + + $val = array(); + foreach($this->nodes as $elem) { + $val[] = $elem->getOrder(); + } + + return (max($val)+1); + } + + function unsetChild($id) { // remove child with given ID from nodes list; unset should destruct it. + foreach($this->nodes as $key=>$elem) { + if($elem->getID() == $id) { + unset($this->nodes[$key]); + return null; + } + } + } + } +?> \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/AdmObjective_class.php b/code/web/app/app_achievements_admin/class/AdmObjective_class.php new file mode 100644 index 000000000..c1a4ebc2d --- /dev/null +++ b/code/web/app/app_achievements_admin/class/AdmObjective_class.php @@ -0,0 +1,75 @@ +sqlQuery("SELECT atom_id FROM ach_atom WHERE atom_objective='".$this->getID()."'"); + $sz = sizeof($res); + for($i=0;$i<$sz;$i++) { + $this->nodes[] = $this->makeChild($res[$i]); + } + } + + private function makeChild($d) { + return new AdmAtom($d,$this); + } + + function insertNode(&$n) { // insert an Atom + $n->insert(); + $this->nodes[] = $n; + } + + function removeNode($id) { // remove an Atom + $res = $this->getNode($id); + if($res != null) { + $res->delete_me(); + $this->unsetChild($id); + } + } + + function updateNode($id,$data) { // update an Atom + $res = $this->getNode($id); + if($res != null) { + #MISSING: set new data + # + $res->update(); + } + } + + function getNode($id) { // find an atom + foreach($this->nodes as $elem) { + if($elem->getID == $id) { + return $elem; + } + } + + return null; + } + + function delete_me() { + global $DBc; + + $DBc->sqlQuery("DELETE FROM ach_objective WHERE ao_id='".$this->getID()."'"); + $DBc->sqlQuery("DELETE FROM ach_player_objective WHERE apo_objective='".$this->getID()."'"); + + foreach($this->nodes as $elem) { + $elem->delete_me(); + } + } + + function update() { + + } + + function insert() { + + } + + function setInDev($tf) { + + } + } +?> \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/AdmPerk_class.php b/code/web/app/app_achievements_admin/class/AdmPerk_class.php new file mode 100644 index 000000000..f0af45979 --- /dev/null +++ b/code/web/app/app_achievements_admin/class/AdmPerk_class.php @@ -0,0 +1,77 @@ +insert(); + $this->nodes[] = $n; + } + + function removeNode($id) { // remove an Objective + $res = $this->getNode($id); + if($res != null) { + $res->delete_me(); + $this->unsetChild($id); + } + } + + function updateNode($id,$data) { // update an Objective + $res = $this->getNode($id); + if($res != null) { + #MISSING: set new data + # + $res->update(); + } + } + + function getNode($id) { // find an Objective + foreach($this->nodes as $elem) { + if($elem->getID == $id) { + return $tmp; + } + } + + return null; + } + + function delete_me() { + global $DBc; + + $DBc->sqlQuery("DELETE FROM ach_perk WHERE ap_id='".$this->getID()."'"); + $DBc->sqlQuery("DELETE FROM ach_player_perk WHERE app_perk='".$this->getID()."'"); + + foreach($this->nodes as $elem) { + $elem->delete_me(); + $this->unsetChild($elem->getID()); + } + } + + function update() { + + } + + function insert() { + + } + + function unsetChild($id) { // remove child with given ID from nodes list; unset should destruct it. + foreach($this->nodes as $key=>$elem) { + if($elem->getID() == $id) { + unset($this->nodes[$key]); + return null; + } + } + } + + function setInDev($tf) { + + } + } +?> \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/CSRAchievement_class.php b/code/web/app/app_achievements_admin/class/CSRAchievement_class.php new file mode 100644 index 000000000..f5cec8840 --- /dev/null +++ b/code/web/app/app_achievements_admin/class/CSRAchievement_class.php @@ -0,0 +1,20 @@ +nodes as $elem) { + $elem->grant($pid); + } + } + + function deny($pid) { + foreach($this->nodes as $elem) { + $elem->deny($pid); + } + } + } +?> \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/CSRAtom_class.php b/code/web/app/app_achievements_admin/class/CSRAtom_class.php new file mode 100644 index 000000000..38f04299a --- /dev/null +++ b/code/web/app/app_achievements_admin/class/CSRAtom_class.php @@ -0,0 +1,22 @@ +id = $data['atom_id']; + } + + function grant($pid) { + $this->clear_all($pid); #empty database + } + + function deny($pid) { + $this->clear_all($pid); #empty database + } + + private function clear_all($pid) { + global $DBc; + $DBc->sqlQuery("DELETE FROM ach_player_atom WHERE apa_atom='".$this->getID()."' AND apa_player='".$pid."'"); + } + } +?> \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/CSRObjective_class.php b/code/web/app/app_achievements_admin/class/CSRObjective_class.php new file mode 100644 index 000000000..7fba5a3b0 --- /dev/null +++ b/code/web/app/app_achievements_admin/class/CSRObjective_class.php @@ -0,0 +1,38 @@ +sqlQuery("SELECT atom_id FROM ach_atom WHERE atom_objective='".$this->getID()."'"); + $sz = sizeof($res); + for($i=0;$i<$sz;$i++) { + $this->nodes[] = new CSRAtom($res[$i]); + } + } + + function grant($pid) { + global $DBc; + + $DBc->sqlQuery("INSERT INTO ach_player_objective (apo_objective,apo_player,apo_date) VALUES ('".$this->getID()."','".$pid."','".time()."')"); + + foreach($this->nodes as $elem) { + $elem->grant($pid); + } + } + + function deny($pid) { + global $DBc; + + $DBc->sqlQuery("DELETE FROM ach_player_objective WHERE apo_objective='".$this->getID()."' AND apo_player='".$pid."'"); + + foreach($this->nodes as $elem) { + $elem->deny($pid); + } + } + } +?> \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/CSRPerk_class.php b/code/web/app/app_achievements_admin/class/CSRPerk_class.php new file mode 100644 index 000000000..69eceacf5 --- /dev/null +++ b/code/web/app/app_achievements_admin/class/CSRPerk_class.php @@ -0,0 +1,28 @@ +sqlQuery("INSERT INTO ach_player_perk (app_perk,app_player,app_date) VALUES ('".$this->getID()."','".$pid."','".time()."')"); + + foreach($this->nodes as $elem) { + $elem->grant(); + } + } + + function deny($pid) { + global $DBc; + + $DBc->sqlQuery("DELETE FROM ach_player_perk WHERE app_perk='".$this->getID()."' AND app_player='".$pid."'"); + + foreach($this->nodes as $elem) { + $elem->deny($pid); + } + } + } +?> \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/CSR_inter.php b/code/web/app/app_achievements_admin/class/CSR_inter.php new file mode 100644 index 000000000..e62b98ef7 --- /dev/null +++ b/code/web/app/app_achievements_admin/class/CSR_inter.php @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/Dispatcher_trait.php b/code/web/app/app_achievements_admin/class/Dispatcher_trait.php new file mode 100644 index 000000000..455c4453f --- /dev/null +++ b/code/web/app/app_achievements_admin/class/Dispatcher_trait.php @@ -0,0 +1,30 @@ +insert(); + $this->nodes[] = $n; + } + + function removeNode($id) { + $res = $this->getNode($id); + if($res != null) { + $res->delete_me(); + $this->removeNode($res); + } + } + + function updateNode($id,$data) { + $res = $this->getNode($id); + if($res != null) { + #MISSING: set new data + # + $res->update(); + } + } + + function getNode($id) { + return $this->getIdx($id); + } + } +?> \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/class/RyzomAdmin_class.php b/code/web/app/app_achievements_admin/class/RyzomAdmin_class.php new file mode 100644 index 000000000..e7361364f --- /dev/null +++ b/code/web/app/app_achievements_admin/class/RyzomAdmin_class.php @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/conf.php b/code/web/app/app_achievements_admin/conf.php index e4e3a504e..2032c079f 100644 --- a/code/web/app/app_achievements_admin/conf.php +++ b/code/web/app/app_achievements_admin/conf.php @@ -3,11 +3,12 @@ die(-1); } - $achConf = array(); + $_CONF = array(); - $achConf['summary_size'] = 12; - $achConf['default_lang'] = 'en'; - $achConf['enable_webig'] = true; - $achConf['enable_offgame'] = true; - $achConf['use_cache'] = false; + $_CONF['app_achievements_path'] = "../app_achievements/"; + $_CONF['image_url'] = "http://www.3025-game.de/special/app_achievements/"; + $_CONF['enable_webig'] = true; + $_CONF['enable_offgame'] = true; + $_CONF['enable_CSR'] = true; + $_CONF['enable_ADM'] = true; ?> \ No newline at end of file diff --git a/code/web/app/app_achievements_admin/include/ach_render_admin.php b/code/web/app/app_achievements_admin/include/ach_render_admin.php new file mode 100644 index 000000000..2e231bfff --- /dev/null +++ b/code/web/app/app_achievements_admin/include/ach_render_admin.php @@ -0,0 +1,171 @@ + + .ach_menu { + display:block; + padding:2px; + border:1px solid #000000; + margin-bottom:2px; + color:#FFFFFF; + } + .ach_menu:hover { + color:orange; + } + + .ach_mspan a { + text-decoration:none; + } + "; + + $html .= "
";
+
+ if($_USER->isAdmin()) {
+ $c .= "Admin + "; + } + if($_USER->isCSR()) { + $c .= "CSR + "; + } + + +#$c .= ach_render_menu(); + +$c .= " |
+ "; + + if($_REQUEST['mode'] == "menu" && $_USER->isAdmin()) { + $menu = new AdmMenu(false); + + if($_REQUEST['act'] == "insert") { + $n = new AdmMenuNode(array(),null); + $n->setID(null); + $n->setDev(1); + $n->setName($_REQUEST['acl_name']); + $n->setImage($_REQUEST['ac_image']); + $n->setParentID($_REQUEST['ac_parent']); + + $menu->insertNode($n); + } + + if($_REQUEST['act'] == "delete") { + $menu->removeNode($_REQUEST['ac_id']); + } + + if($_REQUEST['act'] == "update") { + $menu->updateNode($_REQUEST['ac_id'],array("acl_name"=>$_REQUEST['acl_name'],"ac_image"=>$_REQUEST['ac_image'])); + } + + if($_REQUEST['act'] == "dev") { + $curr = $menu->getNode($_REQUEST['ac_id']); + $curr->setInDev(($_REQUEST['state'] != 1)); + } + + + $c .= adm_render_menu($menu); + } + +#$c .= ach_render_content(); + +$c .= " | +