array()); // forcer des répertoires en dehors des options de ligne de commande private $commands = array(); // forcer des commandes spécifiques à phpdocumentor // les infos de plugins sont enregistrées dedans pour l'index.html de la commande generateFromFile() private $infos_plugins = array(); /** * Description de la documentation à générer * @var string $description */ private $description = ""; /** * Présentation de la documentation à générer * @var string $presentation */ private $presentation = ""; /** * Constructeur * * @param InputInterface $input * @param OutputInterface $output **/ public function __construct(InputInterface $input, OutputInterface $output, $autoloader = null) { $this->input = $input; $this->output = $output; $this->autoloader = $autoloader; $this->setApplicationDirectories(); } /** * Obtient le chemin d'un executable sur le serveur. * * @example * ``` * $cmd = $this->obtenir_commande_serveur('svn'); * if ($cmd) * exec("$cmd ... ... "); * } * ``` * @param string $command * Nom de la commande * @return string * Chemin de la commande **/ public function obtenir_commande_serveur($command) { static $commands = array(); if (array_key_exists($command, $commands)) { return $commands[$command]; } exec("command -v $command", $output, $err); if (!$err and count($output) and $cmd = trim($output[0])) { return $commands[$command] = $cmd; } $this->output->writeln("Commande '$command' introuvable sur ce serveur…"); return $commands[$command] = ''; } /** * Générer la documentation à partir du SVN de SPIP * * Retrouve le nom de la version utilisée et s'en sert de titre. * * @param string $chemin Chemin dans le svn **/ public function generateFromSpip($chemin) { $core = "svn://trac.rezo.net/spip/$chemin"; $prefixe = $this->input->getOption('prefixe'); $ok = $this->createDirectories($prefixe) && $this->getSvnSource($core); if (!$ok) return false; $titre = ""; if ($this->input->hasOption('titre')) { $titre = $this->input->getOption('titre'); } // retrouver la version de SPIP if (!$titre and file_exists($inc_version = $this->dirs['input'] . '/ecrire/inc_version.php')) { $inc_version = file_get_contents($inc_version); if (preg_match('/spip_version_branche = "([^"]+)";/', $inc_version, $res)) { $titre = "SPIP " . $res[1]; } } if ($titre) { $this->setCommand('title', $titre); $this->setOption('titre', $titre); $this->setOption('description', "Documentation du code PHP de " . $titre); } return $this->generateFromDirectory($this->dirs['input'], true); } /** * Générer la documentation à partir du SVN de la Zone de SPIP * * @param string $chemin Chemin dans le svn **/ public function generateFromZone($chemin) { $zone = "svn://zone.spip.org/spip-zone/$chemin"; return $this->generateFromSvn($zone); } /** * Générer la documentation à partir d'une url SVN * * @param string $source URL SVN **/ public function generateFromSvn($source) { $prefixe = $this->input->getOption('prefixe'); $ok = $this->createDirectories($prefixe) && $this->getSvnSource($source) && $this->generateFromDirectory($this->dirs['input'], true); return $ok; } /** * Générer la documentation à partir d'un répertoire indiqué * * @param string $dir Chemin du répertoire source * @param bool $is_ready * Indique si les préparatifs (création des répertoires) sont déjà faits **/ public function generateFromDirectory($dir, $is_ready = false) { $prefixe = $this->input->getOption('prefixe'); $ok = true; if (!$is_ready) { # forcer le chemin de la source spécifique $this->options['dirs']['input'] = $dir; $ok = $this->createDirectories($prefixe); if (!$ok) return false; } $this->retrouverInfoPaquetXml(); $ok = $this->prepareConfigXml() && $this->clearLogs() ; if (!$ok) { return false; } $this->execute(); return true; } /** * Générer la documentation à partir d'un fichier listant les documentations à exécuter * * On duplique le fichier chez soi et on gère une rotation de ce fichier à chaque lancement * afin de supprimer les documentations devenues inutiles (disparues du nouveau fichier). * * @param string $file Chemin du fichier ou URL SVN **/ public function generateFromFile($file) { $timerStart = microtime(true); // Option boussole SPIP => topnav. if ($this->getOption('avec_boussole_spip')) { $this->setOption('topnav', '//boussole.spip.net/?page=spipnav.js&lang=fr'); } // si un prefixe spécifique de plugin est indiqué, // seul lui sera actualisé dans la liste des plugins. // et on considère que le fichier autodoc.txt n'a pas bougé dans le cas. $prefixe = $this->getOption('prefixe', null); // définir les chemins et faire tourner le backup précédent $this->files['autodoc.txt'] = $this->dirs['work'] . '/autodoc.txt'; $this->files['autodoc.txt.bak'] = $this->files['autodoc.txt'] . '.bak'; // à quel endroit sont générées les documentation des fichiers $output_base = $this->input->getOption('sorties'); if (!$output_base) { $output_base = $this->dirs['work'] . '/output'; } $this->setOption('site', '../index.html'); if ($prefixe) { // autodoc.txt est censé être déjà là $this->output->writeln("* Lecture des informations du fichier (sans l'actualiser)"); $presents = $this->parseFile($this->files['autodoc.txt']); // ne conserver que ce prefixe $presents = array_intersect_key($presents, array($prefixe => '')); } else { // autodoc.txt doit être téléchargé ou mis à jour $this->actualiser_liste_source($file); $this->output->writeln("* Lecture des informations du fichier"); $anciens = $this->parseFile($this->files['autodoc.txt.bak'], false); $presents = $this->parseFile($this->files['autodoc.txt']); if ($anciens) { $absents = array_diff_key($anciens, $presents); $this->supprimer_vieux_plugins($absents); } } $nb_erreur = 0; $nb_update = 0; foreach ($presents as $_prefixe => $description) { $ok = $this->obtenir_fichiers_a_documenter($_prefixe, $description); if ($ok) { $nb_update++; $this->execute(); } elseif ($ok === false) { $nb_erreur++; } } $this->output->writeln("\n"); if ($nb_erreur) { $this->output->writeln("$nb_erreur documentation(s) non générée(s) sur " . count($presents) . "."); } else { $this->output->writeln("Documentations générées : " . $nb_update . " / " . count($presents) . "."); } // générer la page sommaire (si pas uniquement 1 seul prefixe actualisé if (!$prefixe) { $this->generer_sommaire_documentations($presents, $output_base); } $this->output->writeln(sprintf("%-'-81s", "")); $this->output->write(sprintf('%-68.68s .. ', "Temps total")); $this->output->writeln(sprintf('%8.3fs', microtime(true) - $timerStart)); $this->output->writeln("Fin"); } /** * Télécharge ou actualise les fichiers d'une documentation * * Si la mise à jour ne modifie pas les fichiers, * la documentation n'est pas à actualiser (retourne null). * * @param string $prefixe * Préfixe de cette documentation * @param array $description * Description. La clé 'source' a l'url SVN * @return bool|null * - True si OK et mise à jour à faire. * - null : ok, mais mise à jour inutile * - False : erreur **/ public function obtenir_fichiers_a_documenter($prefixe, $description) { $this->output->writeln("\n"); $titre = "Générer la documentation de $prefixe"; $this->output->writeln("$titre"); $this->output->writeln("" . str_repeat("-", strlen($titre)) . ""); if (!$this->createDirectories($prefixe)) { $this->output->writeln("* Documentation ignorée à cause d'une erreur."); return false; } $revision_actuelle = $this->recuperer_revision_svn(); if (!$this->getSvnSource($description['source'])) { $this->output->writeln("* Documentation ignorée à cause d'une erreur."); return false; } // ces options sont créées par retrouverInfoPaquetXml() SI elles n'existent pas. // il faut les nettoyer à chaque passage ! $this->setOption('titre', null); $this->setOption('description', null); $this->setOption('presentation', null); // Retrouver les informations de paquet.xml if (!$this->retrouverInfoPaquetXml()) { $this->output->writeln("* Documentation ignorée : paquet.xml introuvable."); return false; } // pas besoin de mettre à jour, si l'update n'a pas augmenté la révision $revision_nouvelle = $this->recuperer_revision_svn(); if ($revision_nouvelle <= $revision_actuelle) { $this->output->writeln("* Documentation déjà à jour."); // sauf si on le force explicitement if (!$this->getOption('force')) { return null; } } $ok = $this->prepareConfigXml() && $this->clearLogs(); if (!$ok) { $this->output->writeln("* Documentation ignorée à cause d'une erreur."); return false; } return true; } /** * Générer le sommaire des documentations réalisés à partir d'un fichier source d'url * * @param array $presents * Liste des plugins du fichier * @param string $outut_base * Répertoire où on été enregistré les documentations * @return **/ public function generer_sommaire_documentations($presents, $output_base) { // générer un sommaire de toutes ces documentations $this->output->writeln("Création du sommaire des documentations"); // ajouter les dépendances du fichier html if (!is_dir($data = $output_base . '/__data')) { mkdir($data); $template = $this->dirs['template']; foreach (array('bootstrap', 'images', 'css', 'js') as $dir) { exec("cp -r '$template/$dir' '$data/$dir'"); } copy("$template/favicon.png", "$output_base/favicon.png"); } if (file_exists($index = "$output_base/index.html")) { unlink($index); } // obtenir les description des plugins $plugins = array(); foreach ($presents as $prefixe => $present) { if (isset($this->infos_plugins[$prefixe])) { $plugins[$prefixe] = $this->infos_plugins[$prefixe]; } else { // ce n'était pas un plugin ou erreur $plugins[$prefixe] = array( 'prefixe' => $prefixe, 'nom' => $prefixe, 'slogan' => '', 'description' => '', 'annuaire' => '', 'documentation' => '', 'developpement' => '', ); } } usort($plugins, function($a, $b) { return ($a['nom'] < $b['nom']) ? -1 : 1; }); // charger Twig, générer la page et l'enregitrer $loader = new \Twig_Loader_Filesystem( $this->dirs['helper'] . '/Template' ); $twig = new \Twig_Environment($loader); $topnav = $this->getOption('topnav', ''); $content = $twig->render('index.html', array( 'titre' => 'Documentation automatique des plugins SPIP', 'plugins' => $plugins, 'topnav' => $topnav, )); file_put_contents($index, $content); } /** * Télécharge la source de liste des documentations à faire dans autodoc.txt * et fait une rotation avec l'ancienne liste * * @param strig $file * URL du fichier source (svn) de la liste des docémentations à générer * @return bool True si ok. **/ public function actualiser_liste_source($file) { // supprimer l'ancienne rotation if (file_exists( $this->files['autodoc.txt.bak'] )) { unlink($this->files['autodoc.txt.bak']); } // rotation if (file_exists( $this->files['autodoc.txt'] )) { copy($this->files['autodoc.txt'], $this->files['autodoc.txt.bak']); unlink($this->files['autodoc.txt']); } // copier le fichier de liste chez soi if (0 === strpos($file, 'svn://')) { $this->output->write("* Obtenir $file "); // si c'est en svn, on l'exporte simplement. if ($res = $this->makeSvnCommand("export $file autodoc.txt", true, $this->dirs['work'])) { $this->output->writeln("[OK]"); } else { $this->output->writeln("[Error]"); throw new \Exception("Impossible de récupérer le fichier SVN : $file"); } } else { if (file_exists($file)) { copy($file, $this->files['autodoc.txt']); } else { throw new \Exception("Impossible de trouver le fichier : $file"); } } return true; } /** * Effacte une ou plusieurs documentations qui ne sont plus listés * dans la description du fichier autodoc.txt * * @param array $absents * Liste [ prefixe => url ] * @return bool * True si au moins une documentation, et ses caches, ont été effacés. **/ public function supprimer_vieux_plugins($absents) { if ($absents) { $this->output->writeln("Certaines documentations ne sont plus à générer."); foreach ($absents as $prefixe=>$absent) { $this->output->writeln("- Effacement de $prefixe."); $this->deleteDirectoryContent($output_base . '/$prefixe', true); $this->deleteDirectoryContent($this->dirs['work'] . 'log/$prefixe', true); $this->deleteDirectoryContent($this->dirs['work'] . 'input/$prefixe', true); $this->deleteDirectoryContent($this->dirs['work'] . 'cache/$prefixe', true); } return true; } return false; } /** * Analyse un fichier listant les documentations à générer. * * @param string $file Chemin du fichier * @param bool $write_errors Ecrire les erreurs dans la console ? * @return array|bool * - false si echec, * - couples (prefixe => array) sinon. Le tableau de description a les clés suivantes : * - ligne : numéro de ligne * - type : 'svn' * - source : url du svn **/ private function parseFile($file, $write_errors = true) { if (!file_exists($file)) return false; if (!$lines = file($file)) return false; $liste = array(); foreach ($lines as $lineno => $line) { if (!$line) continue; $line = trim($line); if (!$line OR $line[0] == '#') continue; $couples = explode(';', $line); $lineno++; if (count($couples) != 2) { if (count($couples) == 1) { $this->output->writeln("Ligne $lineno omise. Le préfixe ne semble pas défini. Contenu : $line"); } else { $this->output->writeln("Ligne $lineno omise. Trop de paramètres indiqués. Contenu : $line"); } continue; } list ($url, $prefixe) = $couples; if (!$url) { $this->output->writeln("Ligne $lineno omise. L'URL ne semble pas définie. Contenu : $line"); continue; } if (!$prefixe) { $this->output->writeln("Ligne $lineno omise. Le préfixe ne semble pas défini. Contenu : $line"); continue; } if (isset($liste[$prefixe])) { $this->output->writeln("Ligne $lineno omise. Le prefixe $prefixe est déjà déclaré ligne : " . $liste[$prefixe]['ligne'] . ""); continue; } // pas d'erreur ! $liste[$prefixe] = array( 'type' => 'svn', 'ligne' => $lineno, 'source' => $url ); } return $liste; } /** * Effacer le contenu du répertoire log. * * On ne gardera du coup que les logs de la prochaine exécution. * * @return bool true si réussi **/ private function clearLogs() { return $this->deleteDirectoryContent($this->dirs['log']); } /** * Définit les répertoires utiles à l'application **/ private function setApplicationDirectories() { # ce répertoire $this->dirs['helper'] = realpath(__DIR__); # executable php de l'application autodoc (extension de l'application phpdocumentor) $this->dirs['bin'] = realpath( $this->dirs['helper'] . '/../../../bin'); # répertoire racine (celui depuis lequel on execute ce script). exec('pwd', $output); $this->dirs['root'] = $output[0]; # répertoire de travail (celui où on écrira tout). $this->dirs['work'] = $this->dirs['root'] . '/work'; # répertoire du template zora $this->dirs['template'] = realpath( $this->dirs['helper'] . '/../../../templates/zora'); # fichier de config xml pour phpdocumentor $this->files['phpdoc.xml'] = $this->dirs['work'] . '/phpdoc.xml'; } /** * Définit et crée les répertoires nécessaires au fonctionnement de ce programme * * @param string $prefixe Préfixe utilisé pour cette génération * @return bool true si réussi **/ private function createDirectories($prefixe) { $_work = $this->dirs['work']; $this->output->writeln("* Vérifier/créer les répertoires de travail dans $_work"); # si un répertoire pour toutes les sorties est indiqué, forcer son utilisation. if ($this->input->hasOption('sorties') and $dir_output = $this->input->getOption('sorties')) { $this->setOption('dirs/output', $dir_output . "/$prefixe"); } # si un répertoire de sortie est indiqué, forcer son utilisation. if ($this->input->hasOption('sortie') and $dir_output = $this->input->getOption('sortie')) { $this->setOption('dirs/output', $dir_output); } foreach (array('output', 'input', 'log', 'cache') as $dir) { // valeur par défaut, en fonction du préfixe $this->dirs[$dir] = "$_work/$dir/$prefixe"; // valeur forcée dans certains cas if ($path = $this->getOption("dirs/$dir")) { $this->dirs[$dir] = $path; } } foreach (array('output', 'input', 'log', 'cache') as $dir) { if (!$this->createDirectory( $this->dirs[$dir] )) { return false; } } return true; } /** * Créer un des répertoires d'utilisation * * Vérifie également que le répertoire est utilisable en écriture. * * @param string $chemin Répertoire à créer * @return bool true si réussi **/ private function createDirectory($dir) { if (!is_dir($dir)) { $this->output->writeln(" - Création du répertoire $dir"); if (!@mkdir($dir, 0755, true)) { $this->output->writeln("Impossible de créer le répertoire : $dir"); $error = error_get_last(); $this->output->writeln($error['message']); return false; } } if (!is_writable($dir)) { $this->output->writeln("Le répertoire $dir n'est pas accessible en écriture"); return false; } return true; } /** * Télécharge ou met à jour la source SVN indiquée * * @param string $source URL de la source SVN * @return bool true si réussi **/ private function getSvnSource($source) { $this->output->writeln("* Obtenir $source"); $this->svn = array(); // si c'est en svn et le bon, on fait svn up simplement. if ($res = $this->makeSvnCommand('info --xml')) { // nous avons un svn… verifions que c'est le bon ! $xml = simplexml_load_string(implode("", $res)); $source_actuelle = (string)$xml->entry->url; if ($source_actuelle == $source) { #$this->output->write(" - Update "); if (!$res = $this->makeSvnCommand("update")) { $this->output->writeln("[Echec Update]"); return false; } $last = array_pop($res); # $this->output->writeln("[OK] ($last)"); } } // erreur, donc pas un dossier svn ou pas le bon else { // on nettoie le contenu pour svn co $this->deleteDirectoryContent($this->dirs['input']); #$this->output->write(" - Checkout "); if (!$res = $this->makeSvnCommand("checkout $source .")) { $this->output->writeln("[Echec Checkout]"); return false; } $last = array_pop($res); if ($last) $last = "($last)"; # $this->output->writeln("[OK] $last"); } // On récupère le numéro de dernière révision, ça peut servir if ($revision = $this->recuperer_revision_svn()) { $this->svn['revision'] = $revision; } return true; } /** * Retrouver le numéro de la dernière révision du dossier de travail * * @return string Numéro de révision (si connu) **/ function recuperer_revision_svn() { $revision = ''; // Ici, on a les bons fichiers SVN à jour // on récupère le numéro de dernière révision if ($res = $this->makeSvnCommand('info --xml')) { // nous avons un svn… verifions que c'est le bon ! $xml = simplexml_load_string(implode("", $res)); $revision = (string)$xml->entry->commit['revision']; } return $revision; } /** * Exécuter la commande svn indiquée * * @param string $cmd Commande SVN * @param bool $no_error true : Envoie les erreurs dans /dev/null * @return mixed|bool false en cas d'erreur **/ private function makeSvnCommand($cmd, $no_error = true, $dir = null) { if (is_null($dir)) { $dir = $this->dirs['input']; } if (!$svn = $this->obtenir_commande_serveur('svn')) { if (!$svn = $this->obtenir_commande_serveur('svnlite')) { return false; } else { $this->output->writeln("Commande 'svnlite' trouvée"); } } $no_error = $no_error ? '2> /dev/null' : ''; exec("cd $dir && $svn $cmd $no_error", $res, $error); if ($error) return false; return $res; } /** * Suppression du contenu d'un repertoire. * * @link https://www.php.net/manual/en/function.rmdir.php#92050 * * @param string $dir Chemin du repertoire * @param string $delete_me Supprimer aussi le répertoire ? * @return bool Suppression reussie. */ function deleteDirectoryContent($dir, $delete_me = false) { if (!file_exists($dir)) return true; if (!is_dir($dir) || is_link($dir)) return @unlink($dir); foreach (scandir($dir) as $item) { if ($item == '.' || $item == '..') continue; if (!$this->deleteDirectoryContent($dir . "/" . $item, true)) { @chmod($dir . "/" . $item, 0777); if (!$this->deleteDirectoryContent($dir . "/" . $item, true)) return false; }; } if ($delete_me) { return @rmdir($dir); } return true; } /** * Obtient une option, soit de la ligne de commande, soit forcée par le générateur * * @param string $name Nom de l'option * @return mixed **/ private function getOption($name, $default = null) { if (strpos($name, '/') === false) { if ($this->input->hasOption($name) and $valeur = $this->input->getOption($name)) { return $valeur; } if (isset($this->options[$name]) and $this->options[$name]) { return $this->options[$name]; } return $default; } $name = explode('/', $name); $pointeur = &$this->options; while (true) { $n = array_shift($name); if (!isset($pointeur[$n])) { return $default; } if (!count($name)) { return $pointeur[$n]; } if (!is_array($pointeur[$n])) { return $default; } $pointeur = &$pointeur[$n]; } } /** * Enregistre une option (forcée par le générateur) * * Permet d'enregistrer avec des chemins par / * * @example * $this->setOption('dirs/output', $dir) * * @param string $name Nom de l'option * @param string $value Valeur de l'option **/ private function setOption($name, $value) { $name = explode('/', $name); $n = array_shift($name); $pointeur = &$this->options; while ($name) { if (!is_array($pointeur[$n])) { $pointeur[$n] = array(); } $pointeur = &$pointeur[$n]; $n = array_shift($name); } $pointeur[$n] = $value; } /** * Récupère une option de commande pour phpDocumentor * * @param string $command Nom de la commande * @return mixed **/ private function getCommand($command) { if (isset($this->commands[$command])) { $val = $this->commands[$command]; return " --$command=\"$val\""; } return null; } /** * Enregistre une option de commande pour phpDocumentor * * @param string $command Nom de la commande * @param string $value Valeur de la commande **/ private function setCommand($command, $value) { $this->commands[$command] = $value; } /** * Retrouver des infos du paquet.xml si on le trouve * * Définir avec le titre, et une présentation (si ce n'est déjà fait). * * @return bool true si on a trouvé un paquet.xml **/ private function retrouverInfoPaquetXml() { $source = $this->dirs['input']; $is_spip = false; if (!file_exists($paquet = $source . '/paquet.xml')) { $is_spip = true; if (!file_exists($paquet = $source . '/ecrire/paquet.xml')) { return false; } } $paquet = simplexml_load_file($paquet); if (!$paquet) { $this->output->writeln("! Aucun paquet.xml"); return false; } $nom = (string)$paquet->nom; $version = (string)$paquet['version']; $prefixe = (string)$paquet['prefix']; $this->infos_plugins[$prefixe] = array( 'nom' => $nom, 'prefixe' => $prefixe, 'slogan' => '', 'description' => '', 'annuaire' => '', 'documentation' => '', 'developpement' => '', ); $documentation = $developpement = $lien_plugins_spip = ''; if (isset($paquet['documentation'])) { $documentation = $paquet['documentation']; $this->infos_plugins[$prefixe]['documentation'] = $documentation; } if (isset($paquet['developpement'])) { $developpement = $paquet['developpement']; $this->infos_plugins[$prefixe]['developpement'] = $developpement; } if (!$is_spip) { $lien_plugins_spip = "https://plugins.spip.net/$prefixe.html"; $this->infos_plugins[$prefixe]['annuaire'] = $lien_plugins_spip; // récupérer les infos dans les chaînes de langue. $langue = $source . "/lang/paquet-$prefixe"."_fr.php"; if (file_exists($langue)) { $GLOBALS['idx_lang'] = 'pour_autodoc'; defined('_ECRIRE_INC_VERSION') || define('_ECRIRE_INC_VERSION', 1); @include $langue; $infos = $GLOBALS['pour_autodoc']; if (is_array($infos)) { if (!$nom and isset($infos["$prefixe" . "_nom"])) { $nom = $infos["$prefixe" . "_nom"]; $this->infos_plugins[$prefixe]['nom'] = $nom; } if (isset($infos["$prefixe" . "_slogan"])) { $this->infos_plugins[$prefixe]['slogan'] = $infos["$prefixe" . "_slogan"]; } if (isset($infos["$prefixe" . "_descriptif"])) { $this->infos_plugins[$prefixe]['descriptif'] = $infos["$prefixe" . "_descriptif"]; } } } } if (!$this->getOption('titre') and $nom) { $this->setOption('titre', $nom); } if (!$this->getOption('presentation')) { if ($is_spip) { $presentation = "Cette documentation est issue du code source PHP de SPIP $version. "; } else { $rev = ""; if (isset($this->svn['revision']) and $revision = $this->svn['revision']) { $rev .= " (révision $revision)"; } $presentation = "Cette documentation est issue du code source PHP du plugin « ".$nom." », version $version$rev. "; } if (!$is_spip OR $documentation OR $developpement) { if (!$is_spip) { $presentation .= "- [Description dans l'annuaire des plugins]($lien_plugins_spip) "; } if ((string)$documentation) { $presentation .= "- [Documentation]($documentation) "; } if ((string)$developpement) { $presentation .= "- [Outil de développement]($developpement) "; } } $this->setOption('presentation', $presentation); } return true; } /** * Utilise le template de configuration phpdoc.xml en modifiant ses variables * et l'enregistre pour qu'il soit utilisé par l'application * * @return bool true si réussi. **/ private function prepareConfigXml() { $this->output->write("* Préparer le fichier phpdoc.xml "); $template = $this->dirs['helper'] . '/phpdoc_helper.xml'; $template = file_get_contents($template); // c'est pas le meilleur endroit pour faire ça… mais bon. if ($this->getOption('avec_boussole_spip')) { $this->setOption('topnav', '//boussole.spip.net/?page=spipnav.js&lang=fr'); } $substitutions = array( '@DIR_CACHE@' => $this->dirs['cache'], '@DIR_OUTPUT@' => $this->dirs['output'], '@DIR_INPUT@' => $this->dirs['input'], '@DIR_LOG@' => $this->dirs['log'], '@DIR_ROOT@' => $this->dirs['root'], '@OPT_TITRE@' => $this->escape_xml_attr($this->getOption('titre', '')), '@OPT_DESCRIPTION@' => $this->escape_xml_attr($this->getOption('description', '')), '@OPT_PRESENTATION@' => $this->escape_xml_attr($this->getOption('presentation', '')), '@OPT_TITRE_ONGLETS@' => $this->escape_xml_attr($this->getOption('titre_onglets', 'Sommaire')), '@OPT_SITE@' => $this->escape_xml_attr($this->getOption('site', '')), '@OPT_TOPNAV@' => $this->escape_xml_attr($this->getOption('topnav', '')), ); $template = str_replace(array_keys($substitutions), array_values($substitutions), $template); $destination = $this->dirs['work'] . '/phpdoc.xml'; @unlink($destination); if (!@file_put_contents($this->files['phpdoc.xml'], $template)) { $this->output->writeln("[Echec]"); return false; } $this->output->writeln("[OK]"); return true; } /** * Exécute l'application phpDocumentor avec le phpdoc.xml qui a été créé * * @return bool true si réussi. **/ private function execute() { $conf = $this->files['phpdoc.xml']; $command = "project:run --config=$conf --parseprivate"; if ($this->getOption('force')) { $command .= " --force"; } if ($title = $this->getCommand('title')) { $command .= $title; } // l'option --config=x de phpdocumentor (contrairement aux autres) n'est lue que via ArgvInput. // de la sorte, pour transmettre cette option, on crée l'argv qui va bien. Ce n'est pas très glorieux ! $command_helper = $_SERVER['argv']; $command = new StringInput("autodoc $command"); // pas moyen d'obtenir le tableau adapté directement… on triche par une réflexion. $reflectedCommand = new \ReflectionClass('Symfony\Component\Console\Input\ArgvInput'); $tokens = $reflectedCommand->getProperty('tokens'); $tokens->setAccessible(true); $_SERVER['argv'] = $tokens->getValue($command); $app = new Autodoc( $this->autoloader ); $app->run(); $_SERVER['argv'] = $command_helper; } /** * Échappe une chaîne pour l'insérer dans un fichier xml, en tant que valeur d'attribut * * @param string $string * @return string **/ private function escape_xml_attr($string) { return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); } }