superadmin && (empty($_['token']) || $_['token']!= sha1(CRYPTKEY) ) ) throw new Exception("Permission denied"); ini_set('max_execution_time', 36000); ob_implicit_flush(true); ini_set('memory_limit', '2048M'); setlocale( LC_ALL, "fr_FR.utf8" ); /* ACTION NECESSITANT UN CLEAN HEADER*/ if(isset($_['action'])){ switch($_['action']){ case 'download_restore': $file = DUMP_DIRECTORY.$_['file']; if(!file_exists($file)) throw new Exception("Le fichier n'existe pas"); if(filesize($file)==0 || filesize($file)<900) throw new Exception("Le fichier est vide ou trop petit, annulation..."); $stream = file_get_contents($file); ob_end_clean(); header_remove(); header("Content-Type: plain/text"); header("Content-Length: " . strlen($stream)); header('Expires: Sun, 01 Jan 2014 00:00:00 GMT'); header('Cache-Control: no-store, no-cache, must-revalidate'); header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); header('Cache-Control: post-check=0, pre-check=0', FALSE); header('Pragma: no-cache'); header("Content-Disposition: "."attachment"."; filename=\"".utf8_decode($_['file'])."\""); echo $stream; exit(); break; } } /* Pour autoriser les droits sur la table a un user : grant_table($base,$table,$user); ex : grant_table('erp','resource_article','erp-maintenance'); */ //Ne pas supprimer cette étape addStep('Dump de la base actuelle',function($mode){ mysqldump(BASE_NAME,BASE_LOGIN,BASE_PASSWORD); }); addStep('Ajout colonne superadmin aux Ranks',function($mode){ Rank::staticQuery('ALTER TABLE `rank` ADD `superadmin` BOOLEAN NOT NULL DEFAULT FALSE AFTER `description`;'); }); global $connected; $connected = null; addStep('Création des rangs superadmin',function($mode){ global $connected; $ranks = array(); foreach(Rank::loadAll() as $rank) $ranks[$rank->label] = $rank; info('Rang Super Admin...'); if(!isset($ranks['Super Admin'])){ info('Création du rang Super Admin...'); //create super admin rank $admin = new Rank(); $admin->label = 'Super Admin'; $admin->description = 'Dispose de tous les accès'; $admin->superadmin = true; $admin->save(); $admin = $admin; } else { info('Rang Super Admin déjà existant...'); $admin = $ranks['Super Admin']; } info('Rang Tout le monde - Anonyme...'); if(!isset($ranks['Tout le monde - Anonyme'])){ info('Création du rang Tout le monde - Anonyme...'); //create all anonymous rank $anonymous = new Rank(); $anonymous->label = 'Tout le monde - Anonyme'; $anonymous->description = 'Utilisateur non connecté à la plateforme'; $anonymous->superadmin = false; $anonymous->save(); $anonymous = $anonymous; } else { info('Rang Tout le monde - Anonyme déjà existant...'); $anonymous = $ranks['Tout le monde - Anonyme']; } info('Rang Tout le monde - Connecté...'); if(!isset($ranks['Tout le monde - Connecté'])){ info('Création du rang Tout le monde - Connecté...'); //create all connected rank $connected = new Rank(); $connected->label = 'Tout le monde - Connecté'; $connected->description = 'Utilisateur connecté à la plateforme'; $connected->superadmin = false; $connected->save(); $connected = $connected; } else { info('Rang Tout le monde - Connecté déjà existant...'); $connected = $ranks['Tout le monde - Connecté']; } info('ID du rang "Tout le monde - Connecté" : '.$connected->id); info('

Définition des permissions pour CRUD sur ces rangs'); foreach (array($admin,$anonymous,$connected) as $rank) { info('Permissions uniquement par le SuperAdmin sur le rang : '.$rank->label); $permission = new Permission; $permission->scope = 'rank'; $permission->uid = $rank->id; $permission->read = 1; $permission->edit = 1; $permission->delete = 1; $permission->configure = 1; $permission->recursive = 1; $permission->targetScope = 'rank'; $permission->targetUid = $admin->id; $permission->save(); } }); addStep('Ajout visibilité par défaut aux utilisateurs connectés pour les menus',function($mode) { global $connected, $conf; if(empty($connected)){ info('Aucun identfiaint de rank Tout le monde - Connecté, annulation...'); return; } info('Attention, spécifier le rang en configuration du plugin Menus & Navigation !!!'); Plugin::need('navigation/MenuItem'); foreach(MenuItem::staticQuery('SELECT * FROM {{table}} WHERE visibility IS NULL OR visibility = ""', array(), true) as $menu){ info('Menu : '.$menu->label.''); $menu->visibility = $connected->id; $menu->save(); } $conf->put('default_menu_visibility', $connected->id); }); addStep('Migration des users Super Admin avec le rang Super Admin',function($mode){ foreach(User::getAll() as $user){ if(!isset($user->superadmin) || empty($user->superadmin)) continue; info('User en Super Admin : '.$user->login.''); $superAdminRank = Rank::load(array('superadmin'=>true)); $ufrs = array(); foreach(UserFirmRank::loadAll(array('user'=>$user->login, 'rank'=>$superAdminRank->id)) as $ufr) $ufrs[$ufr->firm] = $ufr; foreach(Firm::loadAll() as $firm){ if(isset($ufrs[$firm->id])) continue; info('Création du lien UFR pour le combo UFR : '.$user->login.'/'.$firm->label.'/'.$superAdminRank->label); $newUfr = new UserFirmRank; $newUfr->firm = $firm->id; $newUfr->user = $user->login; $newUfr->rank = $superAdminRank->id; $newUfr->save(); } } }); addStep('Suppression de la colonne superadmin de la table user',function($mode){ User::staticQuery('ALTER TABLE {{table}} DROP COLUMN superadmin;'); }); addStep('Client : ajout du champs pseudonyme',function($mode){ User::staticQuery('ALTER TABLE `client` ADD `pseudonym` VARCHAR(225) NULL AFTER `label`;'); }); addStep('Contacts personne : ajout du champs commentaire',function($mode){ ContactPerson::staticQuery('ALTER TABLE {{table}} ADD `comment` TEXT NULL AFTER `state`;'); }); addStep('Stats : alteration table vers nouveau système de connexion (ne fonctionne que pour mysql)', function(){ info('Déplacement ancienne table connexion'); move_table('statistic_connection','statistic_connection_old'); info('Création nouvelle table connexion'); Plugin::need('statistic/Connection'); Connection::create(); info('Migration des connexions'); foreach (Connection::staticQuery('SELECT * FROM statistic_connection_old')->fetchAll() as $value) { $connection = new Connection(); $connection->label = $value['label']; $connection->handler = 'Mysql'; $json = array(); $json["host"] = $value['host']; $json["login"] = $value['login']; $json["password"] = $value['password']; $json["name"] = $value['database']; $connection->meta = json_encode($json); $connection->save(); } }); addStep('Business : Ajout colonne qualification',function($mode){ Rank::staticQuery('ALTER TABLE `business` ADD `qualification` INT(3) NULL AFTER `description`;'); info('Migration des affaires existantes'); Rank::staticQuery("UPDATE business SET qualification = 10 WHERE step IN ('approved','checked','draft','opportunity')"); Rank::staticQuery("UPDATE business SET qualification = 60 WHERE step IN ('client_waiting')"); Rank::staticQuery("UPDATE business SET qualification = 100 WHERE step IN ('invoiced','ordered_client','paid','ready','supplier_waiting','to_invoice','to_prepare','ordered_provider')"); }); addStep('Stats : Ajout colonne meta', function($mode){ User::staticQuery('ALTER TABLE `statistic_widget` ADD `meta` TEXT NULL AFTER `creator`;'); }); addStep('Tickets : Changement structure table pour colonne "from"', function($mode){ Plugin::need('issue/IssueReport'); IssueReport::staticQuery('ALTER TABLE `issue_report` CHANGE `from` `from` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL;'); }); addStep('Contact person : Ajout d\'une colonne tag',function($mode){ User::staticQuery('ALTER TABLE contact_person ADD `tag` TEXT NULL AFTER `uid`;'); }); addStep('Contacts personne : ajout du champs type',function($mode){ ContactPerson::staticQuery('ALTER TABLE {{table}} ADD `type` VARCHAR(225) NULL AFTER `state`;'); ContactPerson::staticQuery('UPDATE {{table}} SET `type`= "user" WHERE account IS NOT NULL AND account !=""'); }); addStep('DynamicForm : ajout du champs firm',function($mode){ Plugin::need('dynamicform/DynamicForm'); DynamicForm::staticQuery('ALTER TABLE {{table}} ADD `firm` INT(11) NULL AFTER `state`;'); DynamicForm::staticQuery('UPDATE {{table}} SET `firm`= 0 '); }); addStep('Firm : Déplacement des logos dans le rep public', function(){ $source = File::dir().'core'.SLASH.'firm'; $destination = File::dir().'core'.SLASH.'public'.SLASH.'firm'; if(!is_dir($source)) throw new Exception("Dossier source inexistant"); File::copy($source, $destination); }); addStep('Droits : fusion permission et droits ', function(){ global $myFirm; info('Création des colonnes targetScope,uid,recursive'); Right::staticQuery('ALTER TABLE {{table}} ADD `targetScope` VARCHAR(225) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL;'); Right::staticQuery('ALTER TABLE {{table}} ADD `uid` INT(11) NULL AFTER `firm`;'); Right::staticQuery('ALTER TABLE {{table}} ADD `recursive` INT(11) NULL AFTER `firm`;'); info('Migrations des colonnes section-> scope, rank->targetUid'); Right::staticQuery('ALTER TABLE {{table}} CHANGE `section` `scope` VARCHAR(225) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL;'); Right::staticQuery('ALTER TABLE {{table}} CHANGE `rank` `targetUid` VARCHAR(225) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL;'); info('Fill des targetScope en "rank"'); Right::staticQuery('UPDATE {{table}} SET targetScope = "rank"'); info('Fill des uid en 0'); Right::staticQuery('UPDATE {{table}} SET uid = 0'); info('Migration de la table permission dans la table right'); $oldPermissions = Right::staticQuery('SELECT * FROM permission',array())->fetchAll(); foreach($oldPermissions as $oldPermission){ $right = new Right(); $right->firm = $myFirm->id; $right->read = $oldPermission['read']; $right->edit = $oldPermission['edit']; $right->delete = $oldPermission['delete']; $right->configure = $oldPermission['configure']; $right->scope = $oldPermission['scope']; $right->uid = $oldPermission['uid']; $right->targetScope = $oldPermission['targetScope']; $right->targetUid = $oldPermission['targetUid']; $right->recursive = $oldPermission['recursive']; $right->save(); } info('Suppression de la table de permissions'); //Right::staticQuery('DROP TABLE IF EXISTS permission'); }); addStep('Client : ajout du champs firm',function($mode){ Plugin::need('client/Client'); Client::staticQuery('ALTER TABLE {{table}} ADD `firm` INT(11) NULL AFTER `state`;'); Client::staticQuery('UPDATE {{table}} SET `firm`= 0 '); }); addStep('Navigation : ajout du champs firm',function($mode){ Plugin::need('navigation/MenuItem'); MenuItem::staticQuery('ALTER TABLE {{table}} ADD `firm` INT(11) NULL AFTER `sort`;'); MenuItem::staticQuery('UPDATE {{table}} SET `firm`= 0 '); }); addStep('Tickets : Changement structure table pour colonne "from"', function($mode){ Plugin::need('issue/IssueReport'); IssueReport::staticQuery('ALTER TABLE `issue_report` CHANGE `from` `from` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL;'); }); addStep('Summary : Ajout de la configuration', function(){ global $conf; $conf->put('summary_files_extension', 'jpg,png,svg,bmp,gif,jpeg,doc,xls,ppt,docx,xlsx,pptx,pdf,msg,eml,txt'); }); addStep('Stats : modification statistic en statistic_widget', function($mode){ User::staticQuery('UPDATE `right` SET scope="statistic_widget" WHERE scope="statistic";'); }); addStep('User : Ajout colonne meta', function(){ User::staticQuery('ALTER TABLE `user` ADD `meta` TEXT NULL AFTER `origin`;'); }); /** SCRIPT DE MIGRATION ICI **/ /* addStep('',function($mode){ }); */ addStep('Changement interclassement des tables et colonnes en utf8_general_ci',function($mode){ global $databases_credentials; $dbConstant = $databases_credentials['local']; info("Récupération des requêtes à passer..."); $query = ' SELECT CONCAT("ALTER DATABASE `",TABLE_SCHEMA,"` CHARACTER SET = utf8 COLLATE = utf8_general_ci;") AS query FROM `TABLES` WHERE TABLE_SCHEMA LIKE "'.$dbConstant['name'].'" AND TABLE_TYPE=\'BASE TABLE\' GROUP BY TABLE_SCHEMA UNION SELECT CONCAT("ALTER TABLE `",TABLE_SCHEMA,"`.`",table_name,"` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;") AS query FROM `TABLES` WHERE TABLE_SCHEMA LIKE "'.$dbConstant['name'].'" AND TABLE_TYPE=\'BASE TABLE\' GROUP BY TABLE_SCHEMA, table_name UNION SELECT CONCAT("ALTER TABLE `",`COLUMNS`.TABLE_SCHEMA,"`.`",`COLUMNS`.table_name, "` CHANGE `",column_name,"` `",column_name,"` ",data_type,"(",character_maximum_length,") CHARACTER SET utf8 COLLATE utf8_general_ci",IF(is_nullable="YES"," NULL"," NOT NULL"),";") AS query FROM `COLUMNS` INNER JOIN `TABLES` ON `TABLES`.table_name = `COLUMNS`.table_name WHERE `COLUMNS`.TABLE_SCHEMA like "'.$dbConstant['name'].'" and data_type in (\'varchar\',\'char\') AND TABLE_TYPE=\'BASE TABLE\' UNION SELECT CONCAT("ALTER TABLE `",`COLUMNS`.TABLE_SCHEMA,"`.`",`COLUMNS`.table_name, "` CHANGE `",column_name,"` `",column_name,"` ",data_type," CHARACTER SET utf8 COLLATE utf8_general_ci",IF(is_nullable="YES"," NULL"," NOT NULL"),";") AS query FROM `COLUMNS` INNER JOIN `TABLES` ON `TABLES`.table_name = `COLUMNS`.table_name WHERE `COLUMNS`.TABLE_SCHEMA like "'.$dbConstant['name'].'" and data_type in (\'text\',\'tinytext\',\'mediumtext\',\'longtext\') AND TABLE_TYPE=\'BASE TABLE\';'; $result = array(); $pdo = new PDO('mysql:host='.$dbConstant['host'].';dbname=information_schema', $dbConstant['login'], $dbConstant['password']); foreach($pdo->query($query)->fetchAll(PDO::FETCH_ASSOC) as $qry){ preg_match("/ALTER TABLE `".$dbConstant['name']."`\.`(.*)`/iU", $qry['query'], $matches); if(!empty($matches)) $result[$matches[1]][] = $qry['query']; else $result['database'][] = $qry['query']; } info("Disclaimer (avant d'afficher toutes les requêtes) : c'est un peu long quand tu vas l'executer dans ton PMA :D"); $allQueries = ''; foreach ($result as $scope => $queries) { info('Requêtes pour '.$scope.' :'); $currentQueries = ''; foreach($queries as $qry) $currentQueries .= $qry.PHP_EOL; info('
'.nl2br($currentQueries).''); $allQueries .= $currentQueries; } info('
Toutes les requêtes en UN
'.nl2br($allQueries).''); }); addStep('Employee : Ajout colonne state.', function($mode){ Plugin::need('employee/Employee'); Employee::staticQuery('ALTER TABLE {{table}} ADD `state` VARCHAR(225) NULL AFTER `comment`;'); Employee::staticQuery('UPDATE {{table}} SET state = "'.Employee::ACTIVE.'";'); }); addStep('Dictionary : Correctif de la faute historique', function($mode){ Dictionary::staticQuery('RENAME TABLE `dictionnary` TO {{table}};'); }); addStep('Host : input client en dynamicForm', function($mode){ Plugin::need('dynamicform/DynamicForm, dynamicform/DynamicField, dynamicform/DynamicValue, host/Machine, client/Client'); $form = DynamicForm::load(array('slug'=>'host-sheet')); if($form)return; $form = new DynamicForm(); $form->slug = 'host-sheet'; $form->label = 'Fiche hébergement'; $form->state = 'published'; $form->creator = 'admin'; $form->updater = 'admin'; $form->firm = 1; $form->save(); $field = DynamicField::load(array('slug'=>'host-sheet-client')); if($field)return; $clientField = new DynamicField(); $clientField->slug = $form->slug . '-client'; $clientField->type = 'client'; $clientField->label = 'Client'; $clientField->state = 'published'; $clientField->creator = 'admin'; $clientField->form = $form->id; $clientField->mandatory = 0; $clientField->readonly = 0; $clientField->row = 0; $clientField->sort = 0; $clientField->column = 0; $clientField->save(); foreach(Machine::loadAll() as $machine) { if(empty($machine->client))continue; $client = Client::getById($machine->client); $clientName = $client ? $client->label : null; $clientValue = new DynamicValue(); $clientValue->field = $clientField->id; $clientValue->value = $machine->client; $clientValue->firm = 1; $clientValue->scope = 'host'; $clientValue->uid = $machine->id; $clientValue->creator = 'admin'; $clientValue->save(); } }); addStep('Plugin : Migration enabled.json vers /file/core/plugin.json', function($mode){ rename(__ROOT__.PLUGIN_PATH.'enabled.json',File::core().SLASH.'plugin.json'); }); /** FIN DU SCRIPT **/ /******************************************************/ /** NE PAS ALLER PLUS LOIN * */ /** Sauf pour modifier le script sandpiper lui même **/ /******************************************************/ ?> Sandpiper get('offline_mode')): ?>
$file"); if(!file_exists($file)) throw new Exception("Le fichier n'existe pas"); if(filesize($file)==0 || filesize($file)<900) throw new Exception("Le fichier est vide ou trop petit, annulation..."); mysqlrestore(BASE_NAME,BASE_LOGIN,BASE_PASSWORD,$file); info("Restauration terminée"); info("Revenir au menu"); break; case 'download_restore': title("Téléchargement"); $file = DUMP_DIRECTORY.$_['file']; info("Téléchargement du fichier $file"); if(!file_exists($file)) throw new Exception("Le fichier n'existe pas"); if(filesize($file)==0 || filesize($file)<900) throw new Exception("Le fichier est vide ou trop petit, annulation..."); info("nope, ça download pas encore pour le moment ¯\_(ツ)_/¯"); // File::downloadFile($file); info("Téléchargement terminé"); info("Revenir au menu"); break; case 'delete_restore': title("Supression de point de restauration"); $file = DUMP_DIRECTORY.$_['file']; info("Suppression de la restauration $file"); if(!file_exists($file)) throw new Exception("Le fichier n'existe pas"); unlink($file); info("Suppression terminée"); info("Revenir au menu"); break; case 'migrate': if(!$myUser->superadmin) throw new Exception("Vous n'avez pas la permission de lancer cette action"); if(!isset($_['steps']) || count($_['steps'])==0) throw new Exception("Aucune étape renseignée, impossible de lancer."); $time_start = microtime(true); title('Lancement de la migration'); info("Mode : ".$_['mode']); foreach ($steps as $slug => $step) { if(!isset($_['steps'][$slug]) || $_['steps'][$slug]!='on' ) continue; title('> '.$step['label']); $callback = $step['callback']; $callback($_['mode']); execution_time($time_start); } title('Migration terminée'); execution_time($time_start); info("Revenir au menu"); break; } }else{ ?>
Migration

Lancer une migration complete avec les étapes :

    $step): ?>

Mode
Go !
Restauration

Aucune restauration disponible, essayez de rafraichir la page Restaurer une des sauvegardes suivantes :

  1. ()

getMessage().' - L'.$e->getLine().' StackTrace
'.$e->getTraceAsString ().''.$lastQuery.'
'); }catch(Exception $e){ error($e->getMessage().' - L'.$e->getLine().' StackTrace
'.$e->getTraceAsString ().'
'); } ob_end_flush(); //Crée une colonne temporaire pour stocker des ids (INT only) function temp_db_column($class, $colName){ if(!$class || !$colName) throw new Exception("Paramètre manquant (temp_db_column)"); $result = $class::staticQuery('SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "'.BASE_NAME.'" AND TABLE_NAME = "'.$class::tableName().'" AND COLUMN_NAME = ? ', array($colName))->fetch(); if(!$result[0]) $class::staticQuery('ALTER TABLE '.$class::tableName().' ADD ? INT NULL', array($colName)); } function execution_time($time_start){ $time_end = microtime(true); $time = $time_end - $time_start; return info("Temps d'execution : ".round($time,4)." secondes"); } //is not null or empty function inoe($column,$row){ return (isset($row[$column]) && !empty(cleanChar($row[$column]))); } //filter array with regex on key function preg_array_key_exists($pattern, $array) { $keys = array_keys($array); return (int) preg_grep($pattern,$keys); } function title($text){ output('

'.$text.'

'); } //Déplace une table function move_table($from,$to){ User::staticQuery('DROP TABLE IF EXISTS '.$to.';'); User::staticQuery('ALTER TABLE '.$from.' RENAME TO '.$to.';'); } //Copie une table function copy_table($from,$to){ User::staticQuery('DROP TABLE IF EXISTS '.$to.';'); User::staticQuery('CREATE TABLE '.$to.' LIKE '.$from.'; INSERT '.$to.' SELECT * FROM '.$from.';'); } //autoriser l'accès a la table pour un user spécifique function grant_table($base,$table,$user){ User::staticQuery("GRANT SELECT,INSERT,UPDATE,REFERENCES ON `".$base."`.`".$table."` TO '".$user."'@'localhost';"); } function info($text){ output(date('H:i:s').' | '.$text.'
'); } function error($text,$type="danger"){ output('
Erreur: '.date('H:i:s').' | '.$text.'
'); } function cleanChar($text){ $text = utf8_encode($text); if(strlen($text)==1 && ord($text)==0) $text = ''; return $text; } function output($text){ echo $text; flush(); ob_flush(); } function addStep($label,$callback){ global $steps; if(!isset($steps)) $steps = array(); $slug = preg_replace('|[^a-z0-9]|i', '-', $label); $slug = mb_strtolower(preg_replace('|-{2,}|i', '-', $slug)); $steps[$slug] = array('label'=>$label,'callback'=>$callback); } function dbextract($sql){ global $db,$lastQuery; $lastQuery = $sql; $query = $db->prepare($sql); $query->execute(); $results = $query->fetchAll(PDO::FETCH_ASSOC); return $results; } function mysqldump($base,$user,$password){ $command = ''; if(PHP_OS =='WINNT'){ if(MYSQL_DUMP_FILE==''){ error("Le dump de la base est impossible sous windows, a moins de remplir la constante 'MYSQL_DUMP_FILE' avec le chemin vers l'executable mysqldump.exe, la migration se poursuit quand même...","warning"); }else{ $command .= '"'.MYSQL_DUMP_FILE.'" '; } }else{ $command .= 'mysqldump '; } $dumpfile = DUMP_DIRECTORY.date('Y-m-d_h-i').".sql"; $command .= "--routines -h ".BASE_HOST." --user=$user --password=$password $base > ".$dumpfile; echo $command; exec($command); if(!file_exists($dumpfile)) throw new Exception("Impossible de créer le backup"); if(filesize($dumpfile)==0) throw new Exception("Le fichier de dump est vide"); } function mysqlrestore($base,$user,$password,$dumpfile){ $command = ''; if(PHP_OS =='WINNT'){ if(MYSQL_DUMP_FILE==''){ error("Le dump de la base est impossible sous windows, a moins de remplir la constante 'MYSQL_DUMP_FILE' avec le chemin vers l'executable mysqldump.exe, la migration se poursuit quand même...","warning"); }else{ $command .= '"'.str_replace('dump.exe','.exe',MYSQL_DUMP_FILE).'" '; } }else{ $command .= 'mysql '; } $command .= " -h ".BASE_HOST." -u {$user} -p{$password} {$base} < $dumpfile"; exec($command); } ?>
get('offline_mode')): ?>