<?php // encoding="UTF-8"
/**

Classe Diple, librairie de méthodes utile à l'affichage des corpus dans diple

© 2010, 2011, École nationale des chartes, licence CeCILL-C (LGPL compatible droit français) */
// autoload function __autoload($Class) { if (is_dir($dir=dirname(dirname(__FILE__))."/modules/$Class/")) return require_once $dir.$Class . '.php'; else require_once dirname(__FILE__)."/".$Class . '.php'; } class Diple { /** Constant used to make function call more readable */ const FORCE=true; /** Constant used to make function call more readable */ const MULTIBOOK=true; /** Requested path */ static $request; /** Absolute filePath of Diple */ static $dipleDir; /** Relative Uri to Diple */ static $dipleHref; /** Corpus code */ static $corpusId; /** Absolute filePath of corpus */ static $corpusDir; /** Relative Uri to the corpus */ static $corpusHref; /** if a book is requested, set the code */ static $bookId; /** A directory where to find generated files */ static $htmlDir; /** A directory where to find doc files */ static $docDir; /** A directory where to find source xml Files */ static $xmlDir; /** An xml File to transform */ static $xmlFile; /** An xsl transfomation for direct html */ static $xslHtml; /** An xsl transfomation for direct html */ static $xslSplit;
/** Récupérer un thème */
static function theme($Class=null, $href=null) { if (!$Class) $Class='neutre'; $dir=mb_strtolower($Class, "utf-8"); $Class=mb_strtoupper(mb_substr($dir, 0, 1, "UTF-8"), "UTF-8").mb_substr($dir, 1, 10000, "UTF-8"); require_once(self::$dipleDir."theme/".$dir.'/'.$Class.'.php'); // pas génial if(!$href) $href=Diple::$dipleHref."theme/"; return new $Class($href); }
/** Ne retourner qu'un seul doc */
static function doc($path=null, $nosplit=false) { $docs=self::docs($path, $nosplit); return $docs[0]; }
/** Interpret URI to get HTML file(s) to include. Dynamic HTML transformations. "" : welcome page from corpus, or doc doc.html : page from doc directory item.html : item from a one file corpus */
static function docs($path=null, $nosplit=false) { if ($path === null) $path=self::$request; // direct service of a file, especially images // is it too much intelligent ? // if ( is_file($file=self::$htmlDir.$path)) Web::get($file); // if ( is_file($file=preg_replace('@/html/@', '/', $file))) Web::get($file); // yes it is, let index.php manage that $docs=array(); // array of docs @list($book, $item)=explode("/", $path); // if nothing requested, welcome page if ($book==="") { $doc=new HtmlInc(self::$htmlDir."index.html"); if ($doc->error) $doc=new HtmlInc(self::$htmlDir."welcome.html"); if ($doc->error) $doc=new HtmlInc(self::$docDir."index.html"); // if found, return, if not, let do the split if (!$doc->error) return array($doc); } else { // needed ! if (file_exists(self::$xmlDir.$book.'.xml')) self::$bookId=$book; } // Pour la doc etc ? if (($book=="src" || $book=="doc") &&$html=self::htmlFile($path)) return array (new HtmlInc($html) ); if ($nosplit); else { // try to generate book or corpus tests are done in the split method self::split(self::$xmlDir."$book.xml", true); self::split(self::$xmlDir.self::$corpusId.".xml"); } // priority to folder if (is_dir($dir=self::$htmlDir.$book."/")) { if ($item==="") { $docs[]=new HtmlInc($dir."index.html"); return $docs; } $idList=explode(",",$item); // allow multiple items displayed with uris like book/id1,id2,id3 foreach ($idList as $id) { $id=trim($id); if ($id === "") continue; // works if id=0 $doc=new HtmlInc($dir.$book."_".$id.".html"); if ($doc->error) $doc=new HtmlInc($dir.$id.".html"); // if no error append the doc to the list if (!$doc->error) $docs[]=$doc; } // if no item found, send message if (!count($docs)) { $docs[0]=new HtmlInc(null); $docs[0]->error="L'item <b>$item</b> semble introuvable dans : <i>$book</i>."; } return $docs; } $idList=explode(",",$book); // allow multiple items displayed with uris like id1,id2,id3 foreach ($idList as $id) { $id=trim($id); if ($id === "") continue; // works if id=0 $doc=new HtmlInc(self::$htmlDir.self::$corpusId."_$id.html"); if ($doc->error) $doc=new HtmlInc(self::$htmlDir."$id.html"); // if no error append the doc to the list if (!$doc->error) $docs[]=$doc; } if (count($docs)) return $docs; // no item found, maybe in doc ? $doc=new HtmlInc(self::$docDir."$book.html"); if (!$doc->error) { $docs[]=$doc; return $docs; } // On demande le schéma if ($book=="schema") $doc=new HtmlInc(self::schema()); // try an html file ? $htmlFile=self::htmlFile(null, null, self::$corpusDir); if ($htmlFile) $doc=new HtmlInc($htmlFile); if ($doc->error) $doc->error="Le document <b>$path</b> semble introuvable."; $docs[]=$doc; return $docs; }
/** Generate HTML pages from a TEI source @param filepath $xmlFile */
static function split($xmlFile, $multibook=null, $force=null) { // regenerate on force reload maybe dangerous on public site // if ($force === null) $force=Web::noCache(); if (!file_exists($xmlFile)) return false; // Inform object user that we fond a source file self::$xmlFile=$xmlFile; // dest html file $xmlBasename=basename($xmlFile, ".xml"); $destFile=self::$htmlDir.$xmlBasename.".html"; // dest html dir if ($multibook===false) $destDir=self::$htmlDir; else if(self::$corpusId == $xmlBasename) $destDir=self::$htmlDir; // multibook else { $destDir=self::$htmlDir.$xmlBasename."/"; $multibook=true; } // dest file newer than xmlFile, go away if (!$force && is_file($destFile) && filemtime($destFile) > filemtime($xmlFile)) return $destDir; self::$xmlFile=$xmlFile; // A file matched, inform object user // split transformation, remplacer les antislashs pour win // split local if (file_exists($xsl=self::$corpusDir."transform/".self::$corpusId."_split.xsl")); // vue html locale, surtout garder cet ordre dans les imports else if (file_exists($f=self::$corpusDir."transform/".self::$corpusId."_html.xsl")) { $xsl='<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:import href="'.strtr($f, array('\\'=>"/")).'"/> <xsl:import href="'.strtr(self::$dipleDir, array('\\'=>"/")).'transform/tei_split.xsl"/> </xsl:transform>'; // a default split transformation } // split par défaut else { $xsl='<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:import href="'.strtr(self::$dipleDir, array('\\'=>"/")).'transform/tei_html.xsl"/> <xsl:import href="'.strtr(self::$dipleDir, array('\\'=>"/")).'transform/tei_split.xsl"/> </xsl:transform>'; // a default split transformation } if (!is_writable($destDir)) { mkdir($destDir, 0775, true); @chmod($destDir, 0775); // laisser @, si www-data a le droit d'écrire mais n'est pas propriétaire // File::clean($destDir); // do not strip all files in folder, some may have been set before } // local theme if ($multibook) $theme="../theme/"; else $theme="theme/"; // Generate HTML files for the book, cache on timestamp, dir param should be absolute for split file projection Transform::xsl ( $xmlFile, // xml file $xsl, $destFile, // dest file array('dir'=> $destDir, 'corpusId'=>self::$corpusId, 'bookId'=>self::$bookId, 'theme'=>$theme), null, $force ); // A split transform have been done copy some static resources for an HTML site if (Transform::$done) { // if destDir is tmp, give an alert to the developper if (strpos($destDir, sys_get_temp_dir()) === 0) echo '<p class="error">Régénération, impossible d’écrire dans <tt>html</tt> ou <tt>var</tt>, pages écrites dans le dossier temporaire : ',$destDir,'</p>'; // make generated files writable by group (verify if it creates problems on OVH) File::scan($destDir, null, null, "chmod", null, array(0664)); // one page transform, cant't be done by the split transform, because of link resolution context $xsl=self::$corpusDir."transform/".self::$corpusId."_html.xsl"; if (!file_exists($xsl)) $xsl=self::$dipleDir."transform/tei_html.xsl"; Transform::xsl ( $xmlFile, // xml file $xsl, $destFile, // dest file array('dir'=> $destDir, 'bookId'=>$xmlBasename, 'corpusId'=>self::$corpusId, 'theme'=>'theme/'), null, true ); File::cp(self::$dipleDir."theme/neutre", self::$htmlDir."theme"); File::cp(self::$dipleDir."js/Tree.js", self::$htmlDir."theme/"); File::cp(self::$corpusDir."theme", self::$htmlDir."theme"); } return $destDir; }
/** Réécriture des liens d'un fichier html statique selon les règles utilisées pour les identifiants */
static function rewrite($html, $base="") { // rewrite static links of included files according to uri policy here $reLinks=array( // '@href="(?<=http://|https://)@' => 'href="'.self::$corpusHref, // trop dangereux '@href="../php/Diple.phps/@' => 'href="'.self::$corpusHref.$base, // liens relatifs déclarés '/href="('.self::$bookId.'|'.self::$corpusId.')_([^"?#]+)\.html/' => 'href="$2', '/href="([A-Za-z0-9\-_]+)\.html/' => 'href="$1', '/ id="(nav|article)"/'=>' id="${1}2"', // avoid html id collisions '@src="@' => 'src="code>, ); return preg_replace( array_keys($reLinks), array_values($reLinks), $html ); }
/** Documentation de schéma Dans le dossier des sources, produit “schema.rng” (un schéma complet sans dépendances avec inclusions résolues) et schema.html, la documentation du schema complet. à faire ? Valider et décliner le schéma dans d'autres formats java -jar $(DIPLE)lib/jing.jar $(RNGFILE); java -jar $(DIPLE)lib/trang.jar $$noinc $(SCHEMA)$$nom.rnc; java -jar $(DIPLE)lib/trang.jar $$noinc $(SCHEMA)$$nom.xsd; */
static function schema($srcDir=false) { if (!$srcDir) $srcDir=self::$xmlDir; $destDir=$srcDir; if (!is_writable($destDir)) $destDir=self::$htmlDir; // files to generate $schemaRng= $destDir."schema.rng"; $schemaHtml=$destDir."schema.html"; // a way to get the first rng file of a folder foreach(glob($srcDir."*.rng") as $xml) { // exclude the dest schema with links resolved if ($xml==$schemaRng) continue; // Schema, resolve inclusion for doc generation Transform::xsl( $xml, self::$dipleDir.'modules/xrem/rng_inc.xsl', $schemaRng, array("inclien"=>"1") ); // test here the timestamps, if transform not done, go out if (!Transform::$done) return $schemaHtml; // alert, on rights if (!is_writable(self::$xmlDir)) { echo '<p class="error">Schéma, les droits ne permettent pas de générer la documentation dans le dossier des sources.</p>'; } // html generation Transform::xsl( $schemaRng, self::$dipleDir.'modules/xrem/rng_html.xsl', $schemaHtml, array("css"=>"../../diple/modules/xrem/xrem.css"), null, true ); // generate a one file schema with no deps, for download, force transform Transform::xsl( $xml, self::$dipleDir.'modules/xrem/rng_inc.xsl', $schemaRng, null, null, true ); break; } return $schemaHtml; }
/** ariane : Génère la fin du fil d'ariane à envoyer à la méthode ariane() du thème string loadAriane(array $htmlIncArray) paramètre $doc : un document html (HtmlInc) ou parfois un tableau valeur de retour $html : un html affichable */
static function ariane($doc, $welcomeFile="index.html,index_fr.html,welcome.html,couverture.html") { $html=""; $count=0; if (!is_array($doc)); else if($count=count($doc) < 1) return $html; // tableau vide, sortir else $doc=$doc[0]; $props=$doc->props(); // si isPartOf, alors le premier contient le titre de corpus, le retirer de la pile if (isset($props['isPartOf'])) { $meta=array_shift($props['isPartOf']); // parfois, le titre de corpus est introuvable if (isset($meta['string'])) { $html = ' <a href="'. $meta['uri']. '">'. $meta['string'] .'</a> »'; } } if (isset($_REQUEST['q'])) return $html." » Recherche";// page de résultat philologic if (strpos( ",".$welcomeFile.",", ",".basename($doc->uri).",") !== false ) return $html; // accueil du corpus, ne rien ajouter if ($count > 1) return $html." » Accueil";// plusieurs doc, résultat d'index // accueil du corpus ? // possible ? if (isset($props['isPartOf'])) { foreach ($props['isPartOf'] as $meta) { $html .= ' <a href="'. $meta['uri']. '">'. $meta['string'] .'</a> » '; } } $html .= ' <a href="">'.$doc->label() . '</a>'; return $html; }
/** Tester s'il ne faut pas rediriger le navigateur en cas de index.php/... */
static function redirect() { $basepath=dirname(realpath($_SERVER['SCRIPT_FILENAME'])).'/'.Diple::$request; // candidate file $index_php=basename($_SERVER['SCRIPT_FILENAME']); $request_uri=$_SERVER['REQUEST_URI']; // the path file exist, redirect to it without index.php/ if (is_file($basepath)) { if ( ($pos=strpos($request_uri, $index_php.'/')) !== false) $location=substr($request_uri, 0, $pos); else $location=$request_uri; header( "Location: $location" ) ; echo '<a href="'.$location.'">Redirection vers '. $location .'</a>'; exit; } // redirection à l'accueil vers …/index.php/ if (Diple::$request=="" && strpos($request_uri, $index_php.'/') === false) { $location=dirname($_SERVER['REQUEST_URI']."o" )."/".basename($_SERVER['SCRIPT_FILENAME'])."/"; header( "Location: $location" ) ; echo '<a href="'.$location.'">redirection vers '. $location .'</a>'; exit; } Diple::$dipleHref="../../diple/"; }
/** Utilisé pour rediriger un stream ob_start Exemple, écrire le contenu généré par une page php dans un fichier Diple::$ob_file=fopen($destFile,'w'); ob_start(array('Diple','ob_file_callback')); require 'file.php'; ob_end_flush(); fclose(Diple::$ob_file); */
protected static $ob_file; static function ob_file_callback($buffer) { fwrite(Diple::$ob_file,$buffer); }
/** Include generated content from php file no Cache */
static function phpHtml($pathinfo=null) { if ($pathinfo === null) $pathinfo=self::$request; $srcPath=self::$xmlDir.$pathinfo; // php File to execute ? if (realpath($file=$srcPath.".php")); else if (realpath($file=$srcPath."/index.php")); else { echo '<!-- Diple::phpHtml not found for :',$srcPath," -->\n"; return false; } // no infinite loop if ($file ==__FILE__) return null; else if ($file == realpath($_SERVER['SCRIPT_FILENAME']) ) return null; $class=basename($file, ".php"); // convention un peu locale, index.php comporte une classe du nom de son dossier if ($class == "index") $class=basename(dirname($file)); // include may change global vars but not constants define('CLASS_TMP',$class); ob_start(); require_once $file; $html= ob_get_contents(); ob_end_clean(); $class=CLASS_TMP; // DO NOT TRY AUTOLOAD if (class_exists($class, false)) define('CLASS_INC',$class); return $html; }
/** Use different Diple resources to cache an html file for different source formats. @param $pathinfo (required) a relative path like in URI without extension @param $destDir (opt) a relative path like in URI without extension @return the html filepath generated */
static function htmlFile($pathinfo=null, $force=null, $srcDir=null, $destDir=null) { // default values if ($pathinfo === null) $pathinfo=self::$request; if ($srcDir === null) $srcDir=self::$xmlDir; if ($destDir === null) $destDir=self::$htmlDir; // if ($force === null) $force=Web::noCache(); // too dangerous in work // a path to test source file presence, strip trailing slash in case of folder $srcPath=rtrim($srcDir.$pathinfo, '/'); // extension, take note of $a=explode(), workaround to a warning $ext=explode(".", $srcPath); $ext=end($ext); // If source file exists, returns its path if (is_file($srcPath)) return $srcPath; // a dest file may be created, generate parent dir of dest files $destFile=$destDir.$pathinfo.".html"; // if RNG, transform if (file_exists($file=$srcPath.".rng")) { return Transform::xsl( $file, // src file dirname(dirname(__FILE__)).'/modules/xrem/rng_html.xsl', // transformation $destFile, // dest file null, // params null, //deps $force // no-cache ? ); } if ( ($ext == "phps") && file_exists($file=substr($srcPath, 0, -1))) $phpdoc="php"; else if (file_exists($file=$srcPath.".css")) $phpdoc="css"; else if (file_exists($file=$srcPath.".js")) $phpdoc="js"; else $phpdoc=null; if ($phpdoc){ if (!file_exists($destFile) || filemtime($destFile) < filemtime($file) || $force) PhpDoc::html($file, $destFile, $phpdoc); return $destFile; } // if (file_exists($file=$srcPath.".php")) return PhpDoc::html($file, $srcPath.$ext, $force); // NO, because php may produce HTML // if XSL, transform if (file_exists($file=$srcPath.".xsl")) { // return the dest file in case of md5 tmpcache return Transform::xsl( $file, // src file dirname(dirname(__FILE__)).'/modules/xrem/xsl_html.xsl', // transformation $destFile, // dest file null, // params null, //deps $force // no-cache ? ); } // if odt, transformer in tei, and in html if (file_exists($srcPath.".odt")) { // compare dates here for cache if (file_exists($destFile) && filemtime($destFile) > filemtime($srcPath.".odt")) return $destFile; $odt_tei=dirname(dirname(__FILE__)).'/modules/odt_tei/'; include_once $odt_tei.'Odt.php'; // write tei here and let work next step ? the dest file is used for images $tei=Odt::tei($srcPath.".odt", $destDir.$pathinfo.".xml"); // return nothing if transformation as a problem return Transform::xsl( $tei, // tei en string dirname(dirname(__FILE__)).'/transform/tei_html.xsl', // transformation tei vers html $destFile, // destination array( "dipleHref"=>self::$dipleHref, "corpusId" =>basename($srcPath), ), // paramètres // dépendances pour date de fraîcheur, le fichier odt, et les transformations odt array ( $srcPath.".odt", $odt_tei.'Odt.php', $odt_tei.'odt_tei.xsl', ), $force ); } // xml file, supposed to be tei, after rng and so on if (file_exists($file=$srcPath.".xml")) { Diple::$xslHtml=Diple::$dipleDir."transform/tei_html.xsl"; if (file_exists($file_xsl=Diple::$corpusDir.'transform/'.Diple::$corpusId.'_html.xsl'))Diple::$xslHtml=$file_xsl; Transform::xsl ( $file, // xml file self::$xslHtml, // transformation $destFile, // dest file null, null, $force ); return $destFile; } // if requested path is a folder (after files), search for a welcome file if (is_dir($srcPath)) { $file=self::htmlFile($pathinfo.'/welcome', $force, $srcDir, $destDir); // test if an index file exist, be careful with index.php if (!$file) $file=self::htmlFile($pathinfo.'/index', $force, $srcDir, $destDir); return $file; // historic ??, /doc, ../, should be fixed upper when calling method } // at the end, in case of cache if (file_exists($file=$srcPath.".htm")) return $file; if (file_exists($file=$srcPath.".html")) return $file; echo "<!-- Diple::htmlFile Failed with src=",$srcPath,"-->\n"; return null; }
/** writeMeta : Renvoie en html les métadonnées du document ou du corpus. string writeMeta(array $corpus, array $htmlIncArray) paramètre $corpus : variable globale array $corpus inscrite dans dc.php et contenant les métadonnées du corpus paramètre $htmlIncArray : tableau des HtmlInc chargés valeur de retour : les métadonnées formatées en html */
static public function writeMeta($corpus, $htmlIncArray) { // on affiche plusieurs documents, renvoyer les métadonnées de corpus. if (count($htmlIncArray) > 1) { foreach ($corpus as $key => $value) { // si plusieurs valeurs pour la même propriété DC if (is_array($value)) { foreach ($value as $lastvalue) echo "<meta name=\"dc.$key\" content=\"$lastvalue\"/>\n"; } elseif ($key == "title" && $value) echo "<title>$value</title>\n"; // titre du corpus elseif ($value) echo "<meta name=\"dc.$key\" content=\"$value\"/>\n"; } } // on affiche un seul document, renvoyer les métadonnées de ce document. elseif ($var=reset($htmlIncArray)) echo $var->meta(); unset($var); } }
/** Set different constants. __FILE__ the php file executed (ex: /home/user/public_html/diple/php/Diple.php) $_SERVER['SCRIPT_FILENAME'] the requested php file (ex: /home/user/public_html/corpus/index.php) $_SERVER['SCRIPT_NAME'] the URI path to the requested php file (ex: /~user/corpus/index.php) $_SERVER['PHP_SELF'] the URI path to the requested php file with pathinfo (ex: /~user/corpus/index.php/book/item) $_SERVER['REQUEST_URI'] the requested URI before rewrite rules applied (ex: /~user/corpus/book/item) */
// Répertoire de Diple par défaut Diple::$dipleDir=dirname(dirname(__FILE__))."/"; // Chemin de ressource demandée Diple::$request=Web::pathinfo(); // Adressage de type corpus.php/..., ou corpus/... if ( ($corpus=basename($_SERVER['SCRIPT_FILENAME'], ".php")) != "index" && is_dir(dirname($_SERVER['SCRIPT_FILENAME'])."/".$corpus) ) { Diple::$corpusId=$corpus; Diple::$corpusDir=dirname($_SERVER['SCRIPT_FILENAME'])."/".$corpus."/"; Diple::$corpusHref=str_repeat("../", substr_count(Diple::$request, '/')); // how much ../../ to get to CORPUS root } // Adressage de type corpus/index.php/... else { Diple::$corpusId=basename(dirname($_SERVER['SCRIPT_FILENAME'])); Diple::$corpusDir=dirname($_SERVER['SCRIPT_FILENAME'])."/"; Diple::$corpusHref=str_repeat("../", substr_count(Diple::$request, '/')); // how much ../../ to get to CORPUS root } // chemin relatif au dossier Diple Diple::$dipleHref=Diple::$corpusHref."../diple/"; // Dossier de documents XML source if (file_exists(Diple::$xmlDir=Diple::$corpusDir."xml/")); else if (file_exists(Diple::$xmlDir=Diple::$corpusDir."src/")); else Diple::$xmlDir=Diple::$corpusDir; // set an alternate doc dir if (file_exists(Diple::$docDir=Diple::$corpusDir."doc/")); require_once dirname(__FILE__).'/File.php'; // ensure a sys_get_temp_dir() function even in php 5.1 // dest dir for generated html files // html dir inside corpus if(is_writable(Diple::$htmlDir=Diple::$corpusDir."html/")); // html files aside source else if (Diple::$xmlDir == Diple::$corpusDir && is_writable(Diple::$htmlDir=Diple::$corpusDir)); // cache dir shared by corporas else if(is_writable($dir=dirname(Diple::$corpusDir).'/cache/') || is_writable($dir=dirname(Diple::$corpusDir).'/html/')) { Diple::$htmlDir=$dir.Diple::$corpusId."/"; if (!is_dir(Diple::$htmlDir)) mkdir(Diple::$htmlDir, 0777, true); chmod(Diple::$htmlDir, 0777); } else { Diple::$htmlDir=sys_get_temp_dir()."/diple/".Diple::$corpusId."/"; if (!file_exists(Diple::$htmlDir)) mkdir(Diple::$htmlDir, 0777, true); chmod(Diple::$htmlDir, 0777); } // Warning: set_time_limit() has been disabled for security reasons @set_time_limit(3600); // regenerate may take time ?>